diff --git a/CHANGELOG.md b/CHANGELOG.md index 458cdcad76..3fee6872e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added helper script for building documentation locally. +- Added new job parameter flag "auditContractNegotiation" which toggles setting contractAgreementId in Shells and Submodels +- Added "contractAgreementId" field to Submodel model +- Added Integration Tests for the entire IRS flow using stubbed responses of Discovery Service, Semantic Hub, EDC, Digital Twin Registry and BPDM Pool ### Changed + +- Dataspace Discovery Service handles multiple EDC-Urls received for BPN now - Updated license header to "Copyright (c) 2021,2024 Contributors to the Eclipse Foundation" - Changed lookupGlobalAssetIds to lookupShellsByBPN, which provides full object. - Suppressed CVE-2024-20932 from graal-sdk-21.2.0.jar because this is not applicable for IRS. +- Redesigned Shell object - wrapped payload and added "contractAgreementId" field +- Changed structure of Policy creation to match EDC format ### Fixed - Update to Spring Boot 3.1.8. This fixes the following CVEs: diff --git a/DEPENDENCIES b/DEPENDENCIES index cc0f92a3fe..a951a92282 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -205,7 +205,6 @@ maven/mavencentral/net.minidev/accessors-smart/2.4.9, Apache-2.0, approved, #751 maven/mavencentral/net.minidev/json-smart/2.4.10, Apache-2.0, approved, #3288 maven/mavencentral/net.minidev/json-smart/2.4.11, Apache-2.0, approved, #3288 maven/mavencentral/net.sf.saxon/Saxon-HE/10.6, MPL-2.0 AND W3C, approved, #7945 -maven/mavencentral/org.apache.commons/commons-compress/1.23.0, Apache-2.0 AND BSD-3-Clause, approved, #7506 maven/mavencentral/org.apache.commons/commons-compress/1.24.0, Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND LicenseRef-Public-Domain, approved, #10368 maven/mavencentral/org.apache.commons/commons-lang3/3.12.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.commons/commons-pool2/2.11.1, Apache-2.0, approved, CQ23795 @@ -325,11 +324,11 @@ maven/mavencentral/org.eclipse.jetty/jetty-xml/11.0.17, EPL-2.0 OR Apache-2.0, a maven/mavencentral/org.eclipse.jetty/jetty-xml/11.0.19, EPL-2.0 OR Apache-2.0, approved, rt.jetty maven/mavencentral/org.eclipse.tractusx.irs/irs-api/0.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx maven/mavencentral/org.eclipse.tractusx.irs/irs-common/0.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.irs/irs-edc-client/1.5.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.irs/irs-models/1.5.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx.irs/irs-edc-client/1.5.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx.irs/irs-models/1.5.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx maven/mavencentral/org.eclipse.tractusx.irs/irs-policy-store/0.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.irs/irs-registry-client/1.5.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.irs/irs-testing/1.5.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx.irs/irs-registry-client/1.5.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx.irs/irs-testing/1.5.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx maven/mavencentral/org.glassfish.hk2.external/aopalliance-repackaged/3.0.4, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish maven/mavencentral/org.glassfish.hk2.external/aopalliance-repackaged/3.0.5, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish maven/mavencentral/org.glassfish.hk2/hk2-api/3.0.4, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish @@ -473,7 +472,7 @@ maven/mavencentral/org.testcontainers/testcontainers/1.19.1, Apache-2.0 AND MIT, maven/mavencentral/org.typelevel/spire-macros_2.13/0.17.0, MIT, approved, clearlydefined maven/mavencentral/org.unbescape/unbescape/1.1.6.RELEASE, Apache-2.0, approved, CQ18904 maven/mavencentral/org.webjars/swagger-ui/5.2.0, Apache-2.0, approved, #10221 -maven/mavencentral/org.wiremock/wiremock-standalone/3.2.0, MIT AND Apache-2.0, approved, #10919 +maven/mavencentral/org.wiremock/wiremock-standalone/3.3.1, MIT AND Apache-2.0, approved, #12941 maven/mavencentral/org.xerial.snappy/snappy-java/1.1.10.5, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #9098 maven/mavencentral/org.xmlunit/xmlunit-core/2.9.1, Apache-2.0, approved, #6272 maven/mavencentral/org.yaml/snakeyaml/1.33, Apache-2.0, approved, clearlydefined diff --git a/README.md b/README.md index 53664b07d4..a34436aa96 100644 --- a/README.md +++ b/README.md @@ -152,3 +152,10 @@ from the base distribution, along with any direct or indirect dependencies of th As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies with any relevant licenses for all software contained within. + +## Contact + +Contact the project developers via the project's "dev" list. + +* https://accounts.eclipse.org/mailing-list/tractusx-dev +* Eclipse Matrix Chat https://chat.eclipse.org/#/room/#tractusx-irs:matrix.eclipse.org \ No newline at end of file diff --git a/docs/concept/#207-redisign-metrics-used-in-summary-inside-job-response/#207-redisign-metrics-used-in-summary-inside-job-response.md b/docs/concept/#207-redisign-metrics-used-in-summary-inside-job-response/#207-redisign-metrics-used-in-summary-inside-job-response.md new file mode 100644 index 0000000000..49217d4ec4 --- /dev/null +++ b/docs/concept/#207-redisign-metrics-used-in-summary-inside-job-response/#207-redisign-metrics-used-in-summary-inside-job-response.md @@ -0,0 +1,55 @@ +# \[Concept\] \[#207\] Redesign metrics used in summary inside Job Response + +| Key | Value | +|---------------|-------------------------------------------------------------------------------| +| Creation date | 31.01.2024 | +| Ticket Id | #207 https://github.com/eclipse-tractusx/item-relationship-service/issues/207 | +| State | DRAFT | + +## Table of Contents + +1. [Overview](#overview) +2. [Problem Statement](#problem-statement) +3. [Concept](#concept) + +## Overview +Currently summary section of a JobResponse contains information about asyncFetchedItems which covers DigitalTwin Registry requests responses and Submodel Server requests responses. +Results are shown together for both of mentioned responses: +``` +"summary": { + "asyncFetchedItems": { + "completed": 1, + "failed": 0, + "running": 0 + } +} +``` + +## Problem Statement +We would like to have separate summary response for DigitalTwin Registry requests and Submodel Server. +Also there should be information about actual tree depth for given JobResponse. + +## Concept +This diagram shows current flow: +![alt text](https://eclipse-tractusx.github.io/item-relationship-service/docs/arc42/architecture-constraints/execute-job.svg) + +Steps number 4 and 6 are respectively requesting AAS and Submodel. Then both are stored in BlobStore by method `addTransferProcess` in `JobStore`. +They should be distinguished by the type, so when step 12 (complete) is executed we can query the store for completed, failed and running items and add them to the response by type. +``` +"summary": { + "asyncFetchedItemsRegistry": { + "running": 0, + "completed": 1, + "failed": 1 + }, + "asyncFetchedItemsSubmodelServer": { + "running": 0, + "completed": 1, + "failed": 1 + } +}, +``` +Tree is stored as list of nodes in list of Relationships as filed `private List relationships;` in `ItemContainer`. +Tree is assebmled in class `ItemTreesAssembler` in method `retrieveItemGraph`. +We should get the depth of the tree and add it to the response. Consider using https://www.geeksforgeeks.org/depth-n-ary-tree/ +Important to note that determining the depth of a running job like this is only suitable as some sort of progress bar. The actual "completion by depth" criteria still has to be evaluated in the DigitalTwinDelegate and needs to be done independently for every branch of the tree diff --git a/docs/src/api/irs-api.yaml b/docs/src/api/irs-api.yaml index 67879e47ea..854d8ef65d 100644 --- a/docs/src/api/irs-api.yaml +++ b/docs/src/api/irs-api.yaml @@ -996,6 +996,7 @@ components: aspects: - SerialPart - AddressAspect + auditContractNegotiation: false bomLifecycle: asBuilt collectAspects: false depth: 1 @@ -1046,6 +1047,7 @@ components: aspects: - SerialPart - AddressAspect + auditContractNegotiation: false bomLifecycle: asBuilt collectAspects: false depth: 1 @@ -1075,7 +1077,9 @@ components: lexicalValue: piece quantityNumber: 1.0 shells: - - description: + - contractAgreementId: a787aa13-2bd7-488f-9e25-40682003901b + payload: + description: - language: en text: The shell for a vehicle globalAssetId: urn:uuid:a45a2246-f6e1-42da-b47d-5c3b58ed62e9 @@ -1168,6 +1172,7 @@ components: aspects: - SerialPart - AddressAspect + auditContractNegotiation: false bomLifecycle: asBuilt collectAspects: false depth: 1 @@ -1194,9 +1199,11 @@ components: lexicalValue: piece quantityNumber: 1.0 shells: - - description: - - language: en - text: The shell for a vehicle + - contractAgreementId: a787aa13-2bd7-488f-9e25-40682003901b + payload: + description: + - language: en + text: The shell for a vehicle globalAssetId: urn:uuid:a45a2246-f6e1-42da-b47d-5c3b58ed62e9 id: urn:uuid:882fc530-b69b-4707-95f6-5dbc5e9baaa8 idShort: future concept x @@ -1318,6 +1325,7 @@ components: aspects: - SerialPart - AddressAspect + auditContractNegotiation: false bomLifecycle: asBuilt collectAspects: false depth: 1 @@ -1355,6 +1363,7 @@ components: aspects: - SerialPart - AddressAspect + auditContractNegotiation: false bomLifecycle: asBuilt collectAspects: false depth: 1 @@ -1381,9 +1390,11 @@ components: lexicalValue: piece quantityNumber: 1.0 shells: - - description: - - language: en - text: The shell for a vehicle + - contractAgreementId: a787aa13-2bd7-488f-9e25-40682003901b + payload: + description: + - language: en + text: The shell for a vehicle globalAssetId: urn:uuid:a45a2246-f6e1-42da-b47d-5c3b58ed62e9 id: urn:uuid:882fc530-b69b-4707-95f6-5dbc5e9baaa8 idShort: future concept x @@ -1469,6 +1480,7 @@ components: aspects: - SerialPart - AddressAspect + auditContractNegotiation: false bomLifecycle: asBuilt collectAspects: false depth: 1 @@ -1676,28 +1688,11 @@ components: leftOperand: type: string example: string - operator: + 'odrl:rightOperand': type: string - example: eq - enum: - - eq - - neq - - lt - - gt - - in - - lteq - - gteq - - isA - - hasPart - - isPartOf - - isOneOf - - isAllOf - - isNoneOf - rightOperand: - type: array - items: - type: string - example: [string] + example: string + operator: + $ref: '#/components/schemas/Operator' Constraints: type: object additionalProperties: false @@ -1715,21 +1710,48 @@ components: additionalProperties: false description: Request to add a policy properties: - permissions: - type: array - description: List of permissions that will be added to the Policy on creation. - items: - $ref: '#/components/schemas/Permission' - policyId: - type: string - description: The ID of the policy to add + payload: + type: object + additionalProperties: + $ref: '#/components/schemas/JsonValue' + example: + payload: + '@context': + odrl: http://www.w3.org/ns/odrl/2/ + '@id': policy-id + policy: + 'odrl:permission': + - 'odrl:action': USE + 'odrl:constraint': + 'odrl:and': + - 'odrl:leftOperand': Membership + 'odrl:operator': + '@id': 'odrl:eq' + 'odrl:rightOperand': active + - 'odrl:leftOperand': PURPOSE + 'odrl:operator': + '@id': 'odrl:eq' + 'odrl:rightOperand': ID 3.1 Trace + validUntil: '2025-12-12T23:59:59.999Z' + properties: + empty: + type: boolean + valueType: + type: string + enum: + - 'ARRAY' + - 'OBJECT' + - 'STRING' + - 'NUMBER' + - 'TRUE' + - 'FALSE' + - 'NULL' validUntil: type: string format: date-time description: Timestamp after which the policy will no longer be accepted in negotiations required: - - permissions - - policyId + - payload - validUntil EdcNotificationResponseNotificationContent: type: object @@ -1975,6 +1997,9 @@ components: aspects: type: string example: SerialPart + auditContractNegotiation: + type: boolean + example: false bomLifecycle: type: string example: asBuilt @@ -2056,7 +2081,7 @@ components: type: array description: AAS shells. items: - $ref: '#/components/schemas/AssetAdministrationShellDescriptor' + $ref: '#/components/schemas/Shell' maxItems: 2147483647 submodels: type: array @@ -2071,6 +2096,39 @@ components: items: $ref: '#/components/schemas/Tombstone' maxItems: 2147483647 + JsonValue: + type: object + additionalProperties: false + properties: + valueType: + type: string + enum: + - 'ARRAY' + - 'OBJECT' + - 'STRING' + - 'NUMBER' + - 'TRUE' + - 'FALSE' + - 'NULL' + example: + payload: + '@context': + odrl: http://www.w3.org/ns/odrl/2/ + '@id': policy-id + policy: + 'odrl:permission': + - 'odrl:action': USE + 'odrl:constraint': + 'odrl:and': + - 'odrl:leftOperand': Membership + 'odrl:operator': + '@id': 'odrl:eq' + 'odrl:rightOperand': active + - 'odrl:leftOperand': PURPOSE + 'odrl:operator': + '@id': 'odrl:eq' + 'odrl:rightOperand': ID 3.1 Trace + validUntil: '2025-12-12T23:59:59.999Z' LangString: type: object additionalProperties: false @@ -2129,6 +2187,27 @@ components: lexicalValue: type: string example: piece + Operator: + type: object + additionalProperties: false + properties: + '@id': + type: string + example: 'odrl:eq' + enum: + - eq + - neq + - lt + - gt + - in + - lteq + - gteq + - isA + - hasPart + - isPartOf + - isOneOf + - isAllOf + - isNoneOf PageResult: type: object additionalProperties: false @@ -2182,10 +2261,8 @@ components: enum: - ACCESS - USE - constraints: - type: array - items: - $ref: '#/components/schemas/Constraints' + constraint: + $ref: '#/components/schemas/Constraints' Policy: type: object additionalProperties: false @@ -2461,6 +2538,10 @@ components: items: type: string maxItems: 2147483647 + auditContractNegotiation: + type: boolean + description: Flag enables and disables auditing, including provisioning + of ContractAgreementId inside submodels and shells objects. Default is true. bomLifecycle: type: string description: The lifecycle context in which the child part was assembled @@ -2541,6 +2622,14 @@ components: value: type: string example: Submodel + Shell: + type: object + additionalProperties: false + properties: + contractAgreementId: + type: string + payload: + $ref: '#/components/schemas/AssetAdministrationShellDescriptor' Submodel: type: object additionalProperties: false @@ -2549,6 +2638,8 @@ components: properties: aspectType: type: string + contractAgreementId: + type: string identification: type: string payload: @@ -2600,6 +2691,10 @@ components: pattern: "^urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" endpointURL: type: string + policy: + type: object + additionalProperties: + type: object processingError: $ref: '#/components/schemas/ProcessingError' AspectModel: diff --git a/docs/src/docs/arc42/cross-cutting/discovery-DTR--EDC-with-multiple-DTRs.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--EDC-with-multiple-DTRs.puml new file mode 100644 index 0000000000..0689cd80a5 --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--EDC-with-multiple-DTRs.puml @@ -0,0 +1,28 @@ +@startuml +participant IRS +participant DiscoveryService +participant "EDC Provider" as EDCProvider +participant "DTR 1" as DTR1 +participant "DTR 2" as DTR2 + +IRS ->> DiscoveryService: Get EDCs for BPN +DiscoveryService ->> IRS: Return list of 1 EDC +IRS ->> EDCProvider: Query for DTR contract offer +EDCProvider ->> IRS: 2 DTR contract offers + +par + group Query DTR 1 + IRS ->> EDCProvider: Negotiate contract + IRS ->> DTR1: Query for DT + DTR1 ->> IRS: no DT + end + + else + + group Query DTR 2 + IRS ->> EDCProvider: Negotiate contract + IRS ->> DTR2: Query for DT + DTR2 ->> IRS: DT + end +end +@enduml \ No newline at end of file diff --git a/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-DTs-with-the-same-globalAssedId-in-one-DTR.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-DTs-with-the-same-globalAssedId-in-one-DTR.puml new file mode 100644 index 0000000000..5956218ecc --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-DTs-with-the-same-globalAssedId-in-one-DTR.puml @@ -0,0 +1,8 @@ +@startuml +actor IRS +participant DTR + +IRS -> DTR: /query for globalAssetId +DTR -> IRS: return list of two results +IRS -> IRS: use first +@enduml diff --git a/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-multiple-DTRs.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-multiple-DTRs.puml new file mode 100644 index 0000000000..be20fd8964 --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-multiple-DTRs.puml @@ -0,0 +1,38 @@ +@startuml +participant IRS +participant DiscoveryService +participant "EDC Provider 1" as EDCProvider1 +participant "EDC Provider 2" as EDCProvider2 +participant "EDC Provider 3" as EDCProvider3 +participant "DTR" as DTR + +IRS ->> DiscoveryService: Get EDCs for BPN +DiscoveryService ->> IRS: Return list of 3 EDCs + +par + group CatalogRequestEDC1 + IRS ->> EDCProvider1: Query for DTR contract offer + EDCProvider1 ->> IRS: No offer + end + + else + + group CatalogRequestEDC2 + IRS ->> EDCProvider2: Query for DTR contract offer + EDCProvider2 ->> IRS: DTR contract offer + IRS -> EDCProvider2: Negotiate contract + IRS ->> DTR: Query for DT + DTR ->> IRS: DT + end + + else + + group CatalogRequestEDC3 + IRS ->> EDCProvider3: Query for DTR contract offer + EDCProvider3 ->> IRS: DTR contract offer + IRS -> EDCProvider3: Negotiate contract + IRS ->> DTR: Query for DT + DTR ->> IRS: No DT + end +end +@enduml diff --git a/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-no-DTRs.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-no-DTRs.puml new file mode 100644 index 0000000000..aaed9497b2 --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-no-DTRs.puml @@ -0,0 +1,34 @@ +@startuml +actor IRS +actor "Discovery Service" as DiscoveryService + +participant "EDC 1" as EDCProvider1 +participant "EDC 2" as EDCProvider2 +participant "EDC 3" as EDCProvider3 + +IRS -> DiscoveryService: Get EDCs for BPN +DiscoveryService -> IRS: Return list of 3 EDCs + +par + group Catalog Request to EDC 1 + IRS -> EDCProvider1: Query for DTR contract offer + EDCProvider1 -> IRS: No offer + end + + else + + group Catalog Request to EDC 2 + IRS -> EDCProvider2: Query for DTR contract offer + EDCProvider2 -> IRS: No offer + end + + else + + group Catalog Request to EDC 3 + IRS -> EDCProvider3: Query for DTR contract offer + EDCProvider3 -> IRS: No offer + end +end + +IRS -> IRS: Tombstone +@enduml diff --git a/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-one-DTR.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-one-DTR.puml new file mode 100644 index 0000000000..f70cc4ac49 --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-one-DTR.puml @@ -0,0 +1,35 @@ +@startuml +participant IRS +participant DiscoveryService +participant "EDC Provider 1" as EDCProvider1 +participant "EDC Provider 2" as EDCProvider2 +participant "EDC Provider 3" as EDCProvider3 +participant DTR + +IRS ->> DiscoveryService: Get EDCs for BPN +DiscoveryService ->> IRS: Return list of 3 EDCs + +par + group CatalogRequestEDC1 + IRS ->> EDCProvider1: Query for DTR contract offer + EDCProvider1 ->> IRS: No offer + end + + else + + group CatalogRequestEDC2 + IRS ->> EDCProvider2: Query for DTR contract offer + EDCProvider2 ->> IRS: No offer + end + + else + + group CatalogRequestEDC3 + IRS ->> EDCProvider3: Query for DTR contract offer + EDCProvider3 ->> IRS: DTR contract offer + IRS -> EDCProvider3: Negotiate contract + IRS ->> DTR: Query for DT + DTR ->> IRS: DT + end +end +@enduml \ No newline at end of file diff --git a/docs/src/docs/arc42/cross-cutting/discovery-DTR--one-EDC-with-one-DTR.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--one-EDC-with-one-DTR.puml new file mode 100644 index 0000000000..8b3277445d --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--one-EDC-with-one-DTR.puml @@ -0,0 +1,14 @@ +@startuml +participant IRS +participant DiscoveryService +participant "EDC Provider 3" as EDCProvider3 +participant DTR + +IRS ->> DiscoveryService: Get EDCs for BPN +DiscoveryService ->> IRS: Return list of 1 EDC +IRS ->> EDCProvider3: Query for DTR contract offer +EDCProvider3 ->> IRS: DTR contract offer +IRS -> EDCProvider3: Negotiate contract +IRS ->> DTR: Query for DT +DTR ->> IRS: DT +@enduml diff --git a/docs/src/docs/arc42/cross-cutting/discovery-process-dtr.adoc b/docs/src/docs/arc42/cross-cutting/discovery-process-dtr.adoc new file mode 100644 index 0000000000..10cea2a582 --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-process-dtr.adoc @@ -0,0 +1,67 @@ +The Dataspace Discovery Service handles multiple EDC-Urls received for BPN. +This applies to the following scenarios. + +__Please note that the expression "the first result" in the subsequent sections means the first successful answer.__ + +==== Scenario 1: EDC with multiple DTRs + +IRS queries all DTRs for the globalAssetId and will take the first result it gets. +If none of the DTRs return a result, IRS will create a tombstone. + +[plantuml,target=discovery-DTR--EDC-with-multiple-DTRs,format=svg] +.... +include::discovery-DTR--EDC-with-multiple-DTRs.puml[] +.... + +==== Scenario 2: Multiple EDCs with one DTR + +IRS starts a contract negotiation for all registry contract offers in parallel and queries the DTRs for all successful negotiations. +The first registry which responds with a DT will be the one used by IRS. + +[plantuml,target=discovery-DTR--multiple-EDCs-with-one-DTR,format=svg] +.... +include::discovery-DTR--multiple-EDCs-with-one-DTR.puml[] +.... + +==== Scenario 3: One EDC with one DTR + +Only one EDC found for BPN and the catalog only contains one offer for the DTR. +IRS will use this registry and will create a tombstone if no DT could be found for the globalAssetId. + +[plantuml,target=discovery-DTR--one-EDC-with-one-DTR,format=svg] +.... +include::discovery-DTR--one-EDC-with-one-DTR.puml[] +.... + +==== Scenario 4: Multiple EDCs with multiple DTRs + +IRS starts a contract negotiation for all the registry offers. + +[plantuml,target=discovery-DTR--multiple-EDCs-with-multiple-DTRs,format=svg] +.... +include::discovery-DTR--multiple-EDCs-with-multiple-DTRs.puml[] +.... + +==== Scenario 5: Multiple EDCs with no DTRs + +IRS starts a contract negotiation for all the registry offers and creates a tombstone since no DTR could be discovered. + +[plantuml,target=discovery-DTR--multiple-EDCs-with-no-DTRs,format=svg] +.... +include::discovery-DTR--multiple-EDCs-with-no-DTRs.puml[] +.... + +==== Special Scenario: Same DT in multiple DTRs + +IRS will use all registries to query for the globalAssetId and takes the first result which is returned. +If no DT could be found in any of the DTRs, IRS will create a tombstone. + +==== Special Scenario: Multiple DTs (with the same globalAssetId) in one DTR + +IRS uses the `/query` endpoint of the DTR to get the DT id based on the globalAssetId. +If more than one id is present for a globalAssetId, IRS will use the first of the list. + +[plantuml,target=discovery-DTR--multiple-DTs-with-the-same-globalAssedId-in-one-DTR,format=svg] +.... +include::discovery-DTR--multiple-DTs-with-the-same-globalAssedId-in-one-DTR.puml[] +.... diff --git a/docs/src/docs/arc42/cross-cutting/under-the-hood.adoc b/docs/src/docs/arc42/cross-cutting/under-the-hood.adoc index aaa8440e10..38419e28cc 100644 --- a/docs/src/docs/arc42/cross-cutting/under-the-hood.adoc +++ b/docs/src/docs/arc42/cross-cutting/under-the-hood.adoc @@ -1,82 +1,115 @@ = "Under-the-hood" concepts == Persistence + The IRS stores two types of data in a persistent way: - Job metadata - Job payloads, e.g. AAS shells or submodel data -All of this is data is stored in an object store. The currently used implementation is Minio (Amazon S3 compatible). -This reduces the complexity in storing and retrieving data. There also is no predefined model for the data, every document can be stored as it is. +All of this is data is stored in an object store. +The currently used implementation is Minio (Amazon S3 compatible). +This reduces the complexity in storing and retrieving data. +There also is no predefined model for the data, every document can be stored as it is. The downside of this approach is lack of query functionality, as we can only search through the keys of the entries but not based on the value data. In the future, another approach or an additional way to to index the data might be required. -To let the data survive system restarts, Minio needs to use a persistent volume for the data storage. A default configuration for this is provided in the Helm charts. +To let the data survive system restarts, Minio needs to use a persistent volume for the data storage. +A default configuration for this is provided in the Helm charts. == Transaction handling + There currently is no transaction management in the IRS. == Session handling + There is no session handling in the IRS, access is solely based on bearer tokens, the API is stateless. == Communication and integration -All interfaces to other systems are using RESTful calls over HTTP(S). Where central authentication is required, a common OAuth2 provider is used. + +All interfaces to other systems are using RESTful calls over HTTP(S). +Where central authentication is required, a common OAuth2 provider is used. For outgoing calls, the Spring RestTemplate mechanism is used and separate RestTemplates are created for the different ways of authentication. For incoming calls, we utilize the Spring REST Controller mechanism, annotating the interfaces accordingly and also documenting the endpoints using OpenAPI annotations. == Exception and error handling + There are two types of potential errors in the IRS: === Technical errors -Technical errors occur when there is a problem with the application itself, its configuration or directly connected infrastructure, e.g. the Minio persistence. Usually, the application cannot solve these problems by itself and requires some external support (manual work or automated recovery mechanisms, e.g. Kubernetes liveness probes). + +Technical errors occur when there is a problem with the application itself, its configuration or directly connected infrastructure, e.g. the Minio persistence. +Usually, the application cannot solve these problems by itself and requires some external support (manual work or automated recovery mechanisms, e.g. Kubernetes liveness probes). These errors are printed mainly to the application log and are relevant for the healthchecks. === Functional errors -Functional errors occur when there is a problem with the data that is being processed or external systems are unavailable and data cannot be sent / fetched as required for the process. While the system might not be able to provide the required function at that moment, it may work with a different dataset or as soon as the external systems recover. + +Functional errors occur when there is a problem with the data that is being processed or external systems are unavailable and data cannot be sent / fetched as required for the process. +While the system might not be able to provide the required function at that moment, it may work with a different dataset or as soon as the external systems recover. These errors are reported in the Job response and do not directly affect application health. === Rules for exception handling + ==== Throw or log, don't do both -When catching an exception, either log the exception and handle the problem or rethrow it, so it can be handled at a higher level of the code. By doing both, an exception might be written to the log multiple times, which can be confusing. + +When catching an exception, either log the exception and handle the problem or rethrow it, so it can be handled at a higher level of the code. +By doing both, an exception might be written to the log multiple times, which can be confusing. ==== Write own base exceptions for (internal) interfaces -By defining a common (checked) base exception for an interface, the caller is forced to handle potential errors, but can keep the logic simple. On the other hand, you still have the possibility to derive various, meaningful exceptions for different error cases, which can then be thrown via the API. + +By defining a common (checked) base exception for an interface, the caller is forced to handle potential errors, but can keep the logic simple. +On the other hand, you still have the possibility to derive various, meaningful exceptions for different error cases, which can then be thrown via the API. Of course, when using only RuntimeExceptions, this is not necessary - but those can be overlooked quite easily, so be careful there. ==== Central fallback exception handler -There will always be some exception that cannot be handled inside of the code correctly - or it may just have been unforeseen. A central fallback exception handler is required so all problems are visible in the log and the API always returns meaningful responses. In some cases, this is as simple as a HTTP 500. + +There will always be some exception that cannot be handled inside of the code correctly - or it may just have been unforeseen. +A central fallback exception handler is required so all problems are visible in the log and the API always returns meaningful responses. +In some cases, this is as simple as a HTTP 500. ==== Dont expose too much exception details over API -It's good to inform the user, why their request did not work, but only if they can do something about it (HTTP 4xx). So in case of application problems, you should not expose details of the problem to the caller. This way, we avoid opening potential attack vectors. -== Parallelization and threading -The heart of the IRS is the parallel execution of planned jobs. As almost each job requires multiple calls to various endpoints, those are done in parallel as well to reduce the total execution time for each job. +It's good to inform the user, why their request did not work, but only if they can do something about it (HTTP 4xx). +So in case of application problems, you should not expose details of the problem to the caller. +This way, we avoid opening potential attack vectors. -Tasks execution is orchestrated by the JobOrchestrator class. It utilizes a central ExecutorService, which manages the number of threads and schedules new Task as they come in. +== Parallelization and threading +The heart of the IRS is the parallel execution of planned jobs. +As almost each job requires multiple calls to various endpoints, those are done in parallel as well to reduce the total execution time for each job. +Tasks execution is orchestrated by the JobOrchestrator class. +It utilizes a central ExecutorService, which manages the number of threads and schedules new Task as they come in. == Plausibility checks and validation + Data validation happens at two points: -- IRS API: the data sent by the client is validated to match the model defined in the IRS. If the validation fails, the IRS sends a HTTP 400 response and indicates the problem to the caller. +- IRS API: the data sent by the client is validated to match the model defined in the IRS. +If the validation fails, the IRS sends a HTTP 400 response and indicates the problem to the caller. - Submodel payload: each time a submodel payload is requested from via EDC, the data is validated against the model defined in the SemanticHub for the matching aspect type. -- EDC Contract Offer Policy: each time IRS consumes data over the EDC, the policies of the offered contract will be validated. IDs of so-called "Rahmenverträgen" or Framework-Agreements can be added to the IRS Policy Store to be accepted by the IRS. If a Contract Offer does not match any of the IDs store in Policy Store, the contract offer will be declined and no data will be consumed. +- EDC Contract Offer Policy: each time IRS consumes data over the EDC, the policies of the offered contract will be validated. +IDs of so-called "Rahmenverträgen" or Framework-Agreements can be added to the IRS Policy Store to be accepted by the IRS. +If a Contract Offer does not match any of the IDs store in Policy Store, the contract offer will be declined and no data will be consumed. == Policy Store -The IRS gives its users the ability to manage, create and delete complex policies containing permissions and constraints in order to obtain the most precise control over access and use of data received from the edc provider. Policies stored in Policy Store will serve as input with allowed restriction and will be checked against every item from EDC Catalog. +The IRS gives its users the ability to manage, create and delete complex policies containing permissions and constraints in order to obtain the most precise control over access and use of data received from the edc provider. +Policies stored in Policy Store will serve as input with allowed restriction and will be checked against every item from EDC Catalog. -The structure of a Policy that can be stored in storage can be easily viewed by using Policy Store endpoints in the published API documentation. Each policy may contain more than one permission, which in turn consists of constraints linked together by AND or OR relationships. This model provides full flexibility and control over stored access and use policies. +The structure of a Policy that can be stored in storage can be easily viewed by using Policy Store endpoints in the published API documentation. +Each policy may contain more than one permission, which in turn consists of constraints linked together by AND or OR relationships. +This model provides full flexibility and control over stored access and use policies. == Digital Twin / EDC requirements -In order to work with the decentral network approach, IRS requires the Digital Twin to contain a `"subprotocolBody"` in each of the submodelDescriptor endpoints. This `"subprotocolBody"` has to contain the `"id"` of the EDC asset, as well as the `"dspEndpoint"` of the EDC, separated by a semicolon (e.g. `"subprotocolBody": "id=123;dspEndpoint=http://edc.control.plane/api/v1/dsp"`). +In order to work with the decentral network approach, IRS requires the Digital Twin to contain a `"subprotocolBody"` in each of the submodelDescriptor endpoints. +This `"subprotocolBody"` has to contain the `"id"` of the EDC asset, as well as the `"dspEndpoint"` of the EDC, separated by a semicolon (e.g. `"subprotocolBody": "id=123;dspEndpoint=http://edc.control.plane/api/v1/dsp"`). The `"dspEndpoint"` is used to request the EDC catalog of the dataprovider and the `"id"` to filter for the exact asset inside this catalog. @@ -94,7 +127,8 @@ Whenever a BPN is resolved via BPDM, the partner name is cached on IRS side, as === Semantics Hub -Whenever a semantic model schema is requested from the Semantic Hub, it is stored locally until the cache is evicted (configurable). The IRS can preload configured schema models on startup to reduce on demand call times. +Whenever a semantic model schema is requested from the Semantic Hub, it is stored locally until the cache is evicted (configurable). +The IRS can preload configured schema models on startup to reduce on demand call times. Additionally, models can be deployed with the system as a backup to the real Semantic Hub service. @@ -109,45 +143,54 @@ The time to live for both caches can be configured separately as described in th Further information on Discovery Service can be found in the chapter "System scope and context". +== Discovery Process + +=== Digital Twin Registry + +include::discovery-process-dtr.adoc[] + === EDC EndpointDataReferenceStorage is in-memory local storage that holds records (EndpointDataReferences) by either assetId or contractAgreementId. When EDC gets EndpointDataReference describing endpoint serving data it uses EndpointDataReferenceStorage and query it by assetId. -This allows reuse of already existing EndpointDataReference if it is present, valid, and it's token is not expired, -rather than starting whole new contract negotiation process. - -In case token is expired the process is also shortened. We don't have to start new contract negotiation process, -since we can obtain required contractAgreementId from present authCode. This improves request processing time. - -[source, mermaid] -.... -sequenceDiagram - autonumber - participant EdcSubmodelClient - participant ContractNegotiationService - participant EndpointDataReferenceStorage - participant EdcCallbackController - participant EdcDataPlaneClient - EdcSubmodelClient ->> EndpointDataReferenceStorage: Get EDR Token for EDC asset id - EndpointDataReferenceStorage ->> EdcSubmodelClient: Return Optional - alt Token is present and not expired - EdcSubmodelClient ->> EdcSubmodelClient: Optional.get - else - alt Token is expired - EdcSubmodelClient ->> ContractNegotiationService: Renew EDR Token based on existing Token - else Token is not present - EdcSubmodelClient ->> ContractNegotiationService: Negotiate new EDR Token - end - ContractNegotiationService -->> EdcCallbackController: EDC flow - EdcCallbackController ->> EndpointDataReferenceStorage: Store EDR token by EDC asset id after EDC callback - loop While EDR Token is not present - EdcSubmodelClient ->> EndpointDataReferenceStorage: Poll for EDR Token - end - EndpointDataReferenceStorage ->> EdcSubmodelClient: Return EDR Token +This allows reuse of already existing EndpointDataReference if it is present, valid, and it's token is not expired, rather than starting whole new contract negotiation process. + +In case token is expired the process is also shortened. +We don't have to start new contract negotiation process, since we can obtain required contractAgreementId from present authCode. +This improves request processing time. + +[plantuml,target=discovery-process-edc,format=svg] +---- +@startuml +autonumber +participant EdcSubmodelClient +participant ContractNegotiationService +participant EndpointDataReferenceStorage +participant EdcCallbackController +participant EdcDataPlaneClient + +EdcSubmodelClient ->> EndpointDataReferenceStorage: Get EDR Token for EDC asset id +EndpointDataReferenceStorage -->> EdcSubmodelClient: Return Optional + +alt Token is present and not expired + EdcSubmodelClient ->> EdcSubmodelClient: Optional.get +else + alt Token is expired + EdcSubmodelClient ->> ContractNegotiationService: Renew EDR Token based on existing Token + else Token is not present + EdcSubmodelClient ->> ContractNegotiationService: Negotiate new EDR Token + end + ContractNegotiationService -->> EdcCallbackController: EDC flow + EdcCallbackController ->> EndpointDataReferenceStorage: Store EDR token by EDC asset id after EDC callback + loop While EDR Token is not present + EdcSubmodelClient ->> EndpointDataReferenceStorage: Poll for EDR Token end - EdcSubmodelClient ->> EdcDataPlaneClient: Get data(EDR Token, Dataplane URL) - EdcDataPlaneClient ->> EdcSubmodelClient: Return data -.... + EndpointDataReferenceStorage -->> EdcSubmodelClient: Return EDR Token +end +EdcSubmodelClient ->> EdcDataPlaneClient: Get data(EDR Token, Dataplane URL) +EdcDataPlaneClient -->> EdcSubmodelClient: Return data +@enduml +---- diff --git a/docs/src/docs/arc42/glossary.adoc b/docs/src/docs/arc42/glossary.adoc index 9fb18f3da7..dbdb67a492 100644 --- a/docs/src/docs/arc42/glossary.adoc +++ b/docs/src/docs/arc42/glossary.adoc @@ -4,20 +4,32 @@ |Term |Description |AAS | Asset Administration Shell (Industry 4.0) -|Aspect servers (submodel endpoints) -|Companies participating in the interorganizational data exchange provides their data over aspect servers. The so called "submodel-descriptors" in the AAS shells are pointing to these AspectServers which provide the data-assets of the participating these companies in Catena-X. -|BoM |Bill of Materials -|Edge |see Traversal Aspect -|IRS |Item Relationship Service +|Aspect servers (submodel endpoints) | Companies participating in the interorganizational data exchange provides their data over aspect servers. The so called "submodel-descriptors" in the AAS shells are pointing to these AspectServers which provide the data-assets of the participating these companies in Catena-X. +|Bill of Materials (BoM) | A Bill of Materials is a comprehensive list of materials, components, sub-assemblies, and the quantities of each needed to manufacture or build a product. It serves as a structured document that provides information about the raw materials, parts, and components required for the production process. +|BPN | Business Partner Number +|Data Space|Data Spaces are the key concept for a large-scale, cross-border data economy. This is also the vision of the Gaia-X initiative for a data infrastructure in Europe. The International Data Space Association (IDSA) contributes significantly to this with the architectural model, interfaces, and standards. +|DT | Digital Twin +|DTR | Digital Twin Registry. The Digital Twin Registry is a registry which lists all digital twins and references their aspects including information about the underlying asset, asset manufacturer, and access options (e.g. aspect endpoints). +|Eclipse Dataspace Connector (EDC) | The Eclipse Data Space Connector (EDC) is a standard and policy-compliant connector that can be used within the scope of Catena-X, but also more generally as a connector for Data Spaces. It is split up into Control-Plane and Data-Plane, whereas the Control-Plane functions as administration layer and has responsibility of resource management, contract negotiation and administer data transfer. The Data-Plane does the heavy lifting of transferring and receiving data streams. For more information see: +https://github.com/eclipse-edc/Connector[EDC Connector] , https://github.com/eclipse-tractusx/tractusx-edc[Tractus-X EDC (Eclipse Dataspace Connector)] +|Edge | see Traversal Aspect +|IRS | Item Relationship Service |Item Graph |The result returned via the IRS. This corresponds to a tree structure in which each node represents a part of a virtual asset. -|MTPDC |Formerly known Service Name: Multi Tier Parts Data Chain -|PRS |Formerly known Service Name: Parts Relationship Service +|Managed Identity Wallet (MIW) +| The Managed Identity Wallets (MIW) service implements the Self-Sovereign-Identity (SSI) readiness by providing a wallet hosting platform including a decentralized identifier (DID) resolver, service endpoints and the company wallets itself. +For more information see: +https://github.com/eclipse-tractusx/managed-identity-wallet[eclipse-tractusx/managed-identity-wallet] , https://github.com/catenax-ng/tx-managed-identity-wallets[catenax-ng/tx-managed-identity-wallets] +|MTPDC | Formerly known Service Name: Multi Tier Parts Data Chain +|PolicyStore +| The Policy Store is an IRS component which provides an interface for getting, adding and deleting accepted IRS EDC policies. These policies will be used to validate EDC contract offers. EDC contract offers must include permissions that are equal to permission defined by an admin user in order to be allowed to use in IRS use cases. +For more information see: +https://github.com/eclipse-tractusx/ssi-docu/blob/main/docs/architecture/cx-3-2/edc/policy.definitions.md#0-introduction[Policy specification for Catena-X verifiable credentials] +|PRS | Formerly known Service Name: Parts Relationship Service +|Self-Sovereign Identity (SSI) +| For more information see: https://github.com/eclipse-tractusx/ssi-docu/tree/main/docs/architecture/cx-3-2[ssi-docu] |Traversal Aspect |aka Edge: Aspect which the IRS uses for traversal through the data chain. Identified by a parent-child or a child-parent relationship. Samples: SingleLevelBomAsPlanned, SingleLevelBomAsBuilt and SingleLevelUsageAsBuilt -|Verifiable Credential (VC) | For more information see: https://github.com/eclipse-tractusx/ssi-docu/tree/main/docs/architecture/cx-3-2/3.%20Verifiable%20Credentials[Verifiable Credentials] -|Eclipse Dataspace Connector (EDC) | For more information see: https://github.com/eclipse-tractusx/tractusx-edc -|Managed Identity Wallet (MIW) | For more information see: https://github.com/eclipse-tractusx/managed-identity-wallet -|Self-Sovereign Identity (SSI) | For more information see: https://github.com/eclipse-tractusx/ssi-docu/tree/main/docs/architecture/cx-3-2 -|PolicyStore | The Policy Store is an IRS component which provides an interface for getting, adding and deleting accepted IRS EDC policies. These policies will be used to validate EDC contract offers. EDC contract offers must include permissions that are equal to permission defined by an admin user in order to be allowed to use in IRS use cases. For more information see: https://github.com/eclipse-tractusx/ssi-docu/blob/main/docs/architecture/cx-3-2/edc/policy.definitions.md#0-introduction +|Verifiable Credential (VC) +| For more information see: https://github.com/eclipse-tractusx/ssi-docu/tree/main/docs/architecture/cx-3-2/3.%20Verifiable%20Credentials[Verifiable Credentials] |=== \ No newline at end of file diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/ItemContainer.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/ItemContainer.java index 135dd58c4e..5cfc633757 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/ItemContainer.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/ItemContainer.java @@ -33,9 +33,9 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.tractusx.irs.component.Bpn; import org.eclipse.tractusx.irs.component.Relationship; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.Submodel; import org.eclipse.tractusx.irs.component.Tombstone; -import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; /** * Container class to store item data @@ -52,7 +52,7 @@ public class ItemContainer { private List tombstones; @Singular - private List shells; + private List shells; @Singular private List submodels; diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/ItemTreesAssembler.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/ItemTreesAssembler.java index e726fba40b..75fe3eaacd 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/ItemTreesAssembler.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/ItemTreesAssembler.java @@ -34,9 +34,9 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.component.Bpn; import org.eclipse.tractusx.irs.component.Relationship; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.Submodel; import org.eclipse.tractusx.irs.component.Tombstone; -import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; /** * Assembles multiple partial item graphs into one overall item graph. @@ -55,7 +55,7 @@ public class ItemTreesAssembler { final var relationships = new LinkedHashSet(); final var numberOfPartialTrees = new AtomicInteger(); final ArrayList tombstones = new ArrayList<>(); - final ArrayList shells = new ArrayList<>(); + final ArrayList shells = new ArrayList<>(); final ArrayList submodels = new ArrayList<>(); final Set bpns = new HashSet<>(); diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java index 2a033b8c6d..5823cff156 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java @@ -40,6 +40,7 @@ import org.eclipse.tractusx.irs.component.assetadministrationshell.Endpoint; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelFacade; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor; import org.eclipse.tractusx.irs.registryclient.discovery.ConnectorEndpointsService; /** @@ -85,31 +86,36 @@ protected ItemContainer next(final ItemContainer.ItemContainerBuilder itemContai return itemContainerBuilder.build(); } - protected String requestSubmodelAsString(final EdcSubmodelFacade submodelFacade, - final ConnectorEndpointsService connectorEndpointsService, final Endpoint endpoint, final String bpn) + protected SubmodelDescriptor requestSubmodel(final EdcSubmodelFacade submodelFacade, + final ConnectorEndpointsService connectorEndpointsService, final Endpoint digitalTwinRegistryEndpoint, + final String bpn) throws EdcClientException { - final String subprotocolBody = endpoint.getProtocolInformation().getSubprotocolBody(); + + final String subprotocolBody = digitalTwinRegistryEndpoint.getProtocolInformation().getSubprotocolBody(); final Optional dspEndpoint = extractDspEndpoint(subprotocolBody); + if (dspEndpoint.isPresent()) { log.debug("Using dspEndpoint of subprotocolBody '{}' to get submodel payload", subprotocolBody); - return submodelFacade.getSubmodelRawPayload(dspEndpoint.get(), endpoint.getProtocolInformation().getHref(), + return submodelFacade.getSubmodelPayload(dspEndpoint.get(), + digitalTwinRegistryEndpoint.getProtocolInformation().getHref(), extractAssetId(subprotocolBody)); } else { log.info("SubprotocolBody does not contain '{}'. Using Discovery Service as fallback.", DSP_ENDPOINT); final List connectorEndpoints = connectorEndpointsService.fetchConnectorEndpoints(bpn); - return getSubmodel(submodelFacade, endpoint, connectorEndpoints); + + return getSubmodel(submodelFacade, digitalTwinRegistryEndpoint, connectorEndpoints); } } - private String getSubmodel(final EdcSubmodelFacade submodelFacade, final Endpoint endpoint, + private SubmodelDescriptor getSubmodel(final EdcSubmodelFacade submodelFacade, final Endpoint digitalTwinRegistryEndpoint, final List connectorEndpoints) throws EdcClientException { for (final String connectorEndpoint : connectorEndpoints) { try { - return submodelFacade.getSubmodelRawPayload(connectorEndpoint, - endpoint.getProtocolInformation().getHref(), - extractAssetId(endpoint.getProtocolInformation().getSubprotocolBody())); + return submodelFacade.getSubmodelPayload(connectorEndpoint, + digitalTwinRegistryEndpoint.getProtocolInformation().getHref(), + extractAssetId(digitalTwinRegistryEndpoint.getProtocolInformation().getSubprotocolBody())); } catch (EdcClientException e) { - log.info("EdcClientException while accessing endpoint '{}'", connectorEndpoint, e); + log.info("EdcClientException while accessing digitalTwinRegistryEndpoint '{}'", connectorEndpoint, e); } } throw new EdcClientException( diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java index af4392bfce..06c655afa6 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java @@ -31,13 +31,12 @@ import org.eclipse.tractusx.irs.aaswrapper.job.ItemContainer; import org.eclipse.tractusx.irs.component.JobParameter; import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.Tombstone; -import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; import org.eclipse.tractusx.irs.component.enums.ProcessStep; import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryKey; import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryService; import org.eclipse.tractusx.irs.registryclient.exceptions.RegistryServiceException; -import org.springframework.web.client.RestClientException; /** * Retrieves AAShell from Digital Twin Registry service and storing it inside {@link ItemContainer}. @@ -55,8 +54,10 @@ public DigitalTwinDelegate(final AbstractDelegate nextStep, } @Override - public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContainerBuilder, final JobParameter jobData, - final AASTransferProcess aasTransferProcess, final PartChainIdentificationKey itemId) { + @SuppressWarnings("PMD.AvoidCatchingGenericException") + public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContainerBuilder, + final JobParameter jobData, final AASTransferProcess aasTransferProcess, + final PartChainIdentificationKey itemId) { if (StringUtils.isBlank(itemId.getBpn())) { log.warn("Could not process item with id {} because no BPN was provided. Creating Tombstone.", @@ -65,23 +66,25 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai Tombstone.from(itemId.getGlobalAssetId(), null, "Can't get relationship without a BPN", 0, ProcessStep.DIGITAL_TWIN_REQUEST)).build(); } + try { - final AssetAdministrationShellDescriptor shell = digitalTwinRegistryService.fetchShells( - List.of(new DigitalTwinRegistryKey(itemId.getGlobalAssetId(), itemId.getBpn()))) - .stream() - .findFirst() - .orElseThrow(); + final Shell shell = digitalTwinRegistryService.fetchShells(List.of(new DigitalTwinRegistryKey(itemId.getGlobalAssetId(), itemId.getBpn()))) + .stream() + .findFirst() + .orElseThrow(); - if (expectedDepthOfTreeIsNotReached(jobData.getDepth(), aasTransferProcess.getDepth())) { - // traversal submodel descriptors are needed in next Delegate, and will be filtered out there - itemContainerBuilder.shell(shell); - } else { + if (!expectedDepthOfTreeIsNotReached(jobData.getDepth(), aasTransferProcess.getDepth())) { // filter submodel descriptors if next delegate will not be executed - itemContainerBuilder.shell(shell.withFilteredSubmodelDescriptors(jobData.getAspects())); + shell.payload().withFilteredSubmodelDescriptors(jobData.getAspects()); } - } catch (final RestClientException | RegistryServiceException e) { + + itemContainerBuilder.shell(jobData.isAuditContractNegotiation() ? shell : shell.withoutContractAgreementId()); + } catch (final RegistryServiceException | RuntimeException e) { + // catching generic exception is intended here, + // otherwise Jobs stay in state RUNNING forever log.info("Shell Endpoint could not be retrieved for Item: {}. Creating Tombstone.", itemId); - itemContainerBuilder.tombstone(Tombstone.from(itemId.getGlobalAssetId(), null, e, retryCount, ProcessStep.DIGITAL_TWIN_REQUEST)); + itemContainerBuilder.tombstone( + Tombstone.from(itemId.getGlobalAssetId(), null, e, retryCount, ProcessStep.DIGITAL_TWIN_REQUEST)); } if (expectedDepthOfTreeIsNotReached(jobData.getDepth(), aasTransferProcess.getDepth())) { diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegate.java index 86b47191d3..231b6d5b31 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegate.java @@ -42,6 +42,7 @@ import org.eclipse.tractusx.irs.edc.client.EdcSubmodelFacade; import org.eclipse.tractusx.irs.edc.client.RelationshipAspect; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.edc.client.exceptions.UsagePolicyException; import org.eclipse.tractusx.irs.registryclient.discovery.ConnectorEndpointsService; import org.eclipse.tractusx.irs.util.JsonUtil; @@ -76,7 +77,7 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai .getShells() .stream() .findFirst() - .ifPresent(shell -> shell.findRelationshipEndpointAddresses( + .ifPresent(shell -> shell.payload().findRelationshipEndpointAddresses( AspectType.fromValue(relationshipAspect.getName())) .forEach(endpoint -> processEndpoint(endpoint, relationshipAspect, aasTransferProcess, itemContainerBuilder, itemId))); @@ -98,8 +99,8 @@ private void processEndpoint(final Endpoint endpoint, final RelationshipAspect r } try { - final String submodelRawPayload = requestSubmodelAsString(submodelFacade, connectorEndpointsService, - endpoint, itemId.getBpn()); + final String submodelRawPayload = requestSubmodel(submodelFacade, connectorEndpointsService, + endpoint, itemId.getBpn()).getPayload(); final var relationships = jsonUtil.fromString(submodelRawPayload, relationshipAspect.getSubmodelClazz()) .asRelationships(); @@ -112,17 +113,22 @@ private void processEndpoint(final Endpoint endpoint, final RelationshipAspect r aasTransferProcess.addIdsToProcess(idsToProcess); itemContainerBuilder.relationships(relationships); itemContainerBuilder.bpns(getBpnsFrom(relationships)); + } catch (final UsagePolicyException e) { + log.info("Encountered usage policy exception: {}. Creating Tombstone.", e.getMessage()); + itemContainerBuilder.tombstone( + Tombstone.from(itemId.getGlobalAssetId(), endpoint.getProtocolInformation().getHref(), e, + 0, ProcessStep.USAGE_POLICY_VALIDATION, jsonUtil.asMap(e.getPolicy()))); } catch (final EdcClientException e) { log.info("Submodel Endpoint could not be retrieved for Endpoint: {}. Creating Tombstone.", endpoint.getProtocolInformation().getHref()); itemContainerBuilder.tombstone( Tombstone.from(itemId.getGlobalAssetId(), endpoint.getProtocolInformation().getHref(), e, - retryCount, ProcessStep.SUBMODEL_REQUEST)); + 0, ProcessStep.SUBMODEL_REQUEST)); } catch (final JsonParseException e) { log.info("Submodel payload did not match the expected AspectType. Creating Tombstone."); itemContainerBuilder.tombstone( Tombstone.from(itemId.getGlobalAssetId(), endpoint.getProtocolInformation().getHref(), e, - retryCount, ProcessStep.SUBMODEL_REQUEST)); + 0, ProcessStep.SUBMODEL_REQUEST)); } } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegate.java index ae74fb4e3c..a75fcb400a 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegate.java @@ -49,6 +49,7 @@ import org.eclipse.tractusx.irs.services.validation.SchemaNotFoundException; import org.eclipse.tractusx.irs.services.validation.ValidationResult; import org.eclipse.tractusx.irs.util.JsonUtil; +import org.jetbrains.annotations.Nullable; import org.springframework.web.client.RestClientException; /** @@ -82,22 +83,22 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai final PartChainIdentificationKey itemId) { itemContainerBuilder.build().getShells().stream().findFirst().ifPresent(shell -> { - final List aasSubmodelDescriptors = shell.getSubmodelDescriptors(); + final List aasSubmodelDescriptors = shell.payload().getSubmodelDescriptors(); log.info("Retrieved {} SubmodelDescriptor for itemId {}", aasSubmodelDescriptors.size(), itemId); - final List filteredSubmodelDescriptorsByAspectType = shell.filterDescriptorsByAspectTypes( + final List filteredSubmodelDescriptorsByAspectType = shell.payload().filterDescriptorsByAspectTypes( jobData.getAspects()); if (jobData.isCollectAspects()) { log.info("Collecting Submodels."); filteredSubmodelDescriptorsByAspectType.forEach(submodelDescriptor -> itemContainerBuilder.submodels( getSubmodels(submodelDescriptor, itemContainerBuilder, itemId.getGlobalAssetId(), - itemId.getBpn()))); + itemId.getBpn(), jobData.isAuditContractNegotiation()))); } log.debug("Unfiltered SubmodelDescriptor: {}", aasSubmodelDescriptors); log.debug("Filtered SubmodelDescriptor: {}", filteredSubmodelDescriptorsByAspectType); - shell.setSubmodelDescriptors(filteredSubmodelDescriptorsByAspectType); + shell.payload().setSubmodelDescriptors(filteredSubmodelDescriptorsByAspectType); }); @@ -105,7 +106,8 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai } private List getSubmodels(final SubmodelDescriptor submodelDescriptor, - final ItemContainer.ItemContainerBuilder itemContainerBuilder, final String itemId, final String bpn) { + final ItemContainer.ItemContainerBuilder itemContainerBuilder, final String itemId, final String bpn, + final boolean auditContractNegotiation) { final List submodels = new ArrayList<>(); submodelDescriptor.getEndpoints().forEach(endpoint -> { @@ -118,15 +120,16 @@ private List getSubmodels(final SubmodelDescriptor submodelDescriptor, try { final String jsonSchema = semanticsHubFacade.getModelJsonSchema(submodelDescriptor.getAspectType()); - final String submodelRawPayload = requestSubmodelAsString(submodelFacade, connectorEndpointsService, - endpoint, bpn); + final org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor submodel = requestSubmodel( + submodelFacade, connectorEndpointsService, endpoint, bpn); + final String submodelRawPayload = submodel.getPayload(); + final String contractAgreementId = getContractAgreementId(auditContractNegotiation, submodel); final ValidationResult validationResult = jsonValidatorService.validate(jsonSchema, submodelRawPayload); if (validationResult.isValid()) { - final Submodel submodel = Submodel.from(submodelDescriptor.getId(), - submodelDescriptor.getAspectType(), jsonUtil.fromString(submodelRawPayload, Map.class)); - submodels.add(submodel); + submodels.add(Submodel.from(submodelDescriptor.getId(), submodelDescriptor.getAspectType(), + contractAgreementId, jsonUtil.fromString(submodelRawPayload, Map.class))); } else { final String errors = String.join(", ", validationResult.getValidationErrors()); itemContainerBuilder.tombstone(Tombstone.from(itemId, endpoint.getProtocolInformation().getHref(), @@ -144,7 +147,7 @@ private List getSubmodels(final SubmodelDescriptor submodelDescriptor, } catch (final UsagePolicyException e) { log.info("Encountered usage policy exception: {}. Creating Tombstone.", e.getMessage()); itemContainerBuilder.tombstone(Tombstone.from(itemId, endpoint.getProtocolInformation().getHref(), e, 0, - ProcessStep.USAGE_POLICY_VALIDATION)); + ProcessStep.USAGE_POLICY_VALIDATION, jsonUtil.asMap(e.getPolicy()))); } catch (final EdcClientException e) { log.info("Submodel Endpoint could not be retrieved for Item: {}. Creating Tombstone.", itemId); itemContainerBuilder.tombstone(Tombstone.from(itemId, endpoint.getProtocolInformation().getHref(), e, 0, @@ -154,4 +157,10 @@ private List getSubmodels(final SubmodelDescriptor submodelDescriptor, return submodels; } + @Nullable + private String getContractAgreementId(final boolean auditContractNegotiation, + final org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor submodel) { + return auditContractNegotiation ? submodel.getCid() : null; + } + } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/OpenApiExamples.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/OpenApiExamples.java index 4314cea529..7365543464 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/OpenApiExamples.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/OpenApiExamples.java @@ -48,6 +48,7 @@ import org.eclipse.tractusx.irs.component.ProcessingError; import org.eclipse.tractusx.irs.component.Quantity; import org.eclipse.tractusx.irs.component.Relationship; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.Submodel; import org.eclipse.tractusx.irs.component.Summary; import org.eclipse.tractusx.irs.component.Tombstone; @@ -371,22 +372,23 @@ private Tombstone createTombstone() { .build(); } - private AssetAdministrationShellDescriptor createShell() { - return AssetAdministrationShellDescriptor.builder() - .description(List.of(LangString.builder() + private Shell createShell() { + return new Shell("a787aa13-2bd7-488f-9e25-40682003901b", + AssetAdministrationShellDescriptor.builder() + .description(List.of(LangString.builder() .language("en") .text("The shell for a vehicle") .build())) - .globalAssetId("urn:uuid:a45a2246-f6e1-42da-b47d-5c3b58ed62e9") - .idShort("future concept x") - .id("urn:uuid:882fc530-b69b-4707-95f6-5dbc5e9baaa8") - .specificAssetIds(List.of(IdentifierKeyValuePair.builder() + .globalAssetId("urn:uuid:a45a2246-f6e1-42da-b47d-5c3b58ed62e9") + .idShort("future concept x") + .id("urn:uuid:882fc530-b69b-4707-95f6-5dbc5e9baaa8") + .specificAssetIds(List.of(IdentifierKeyValuePair.builder() .name("engineserialid") .value("12309481209312") .build())) - .submodelDescriptors(List.of(createBaseSubmodelDescriptor(), + .submodelDescriptors(List.of(createBaseSubmodelDescriptor(), createPartSubmodelDescriptor())) - .build(); + .build()); } private Relationship createRelationship() { diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RestTemplateConfig.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RestTemplateConfig.java index e8204cb21f..acc63e6c95 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RestTemplateConfig.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RestTemplateConfig.java @@ -41,6 +41,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; @@ -66,6 +67,7 @@ @Configuration @RequiredArgsConstructor @SuppressWarnings("PMD.ExcessiveImports") +@Profile("!integrationtest") public class RestTemplateConfig { public static final String DTR_REST_TEMPLATE = "dtrRestTemplate"; diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryService.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryService.java index 6943370891..c8510d6849 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryService.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryService.java @@ -57,10 +57,10 @@ import org.eclipse.tractusx.irs.component.PageResult; import org.eclipse.tractusx.irs.component.RegisterJob; import org.eclipse.tractusx.irs.component.Relationship; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.Submodel; import org.eclipse.tractusx.irs.component.Summary; import org.eclipse.tractusx.irs.component.Tombstone; -import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; import org.eclipse.tractusx.irs.component.enums.BomLifecycle; import org.eclipse.tractusx.irs.component.enums.Direction; import org.eclipse.tractusx.irs.component.enums.JobState; @@ -245,7 +245,7 @@ public Jobs getJobForJobId(final UUID jobId, final boolean includePartialResults public Jobs getJobForJobId(final MultiTransferJob multiJob, final boolean includePartialResults) { final var relationships = new ArrayList(); final var tombstones = new ArrayList(); - final var shells = new ArrayList(); + final var shells = new ArrayList(); final var submodels = new ArrayList(); final var bpns = new ArrayList(); @@ -332,7 +332,7 @@ private ItemContainer retrievePartialResults(final MultiTransferJob multiJob) { final var relationships = new ArrayList(); final var tombstones = new ArrayList(); - final var shells = new ArrayList(); + final var shells = new ArrayList(); final var submodels = new ArrayList(); final var metrics = new ArrayList(); final var bpns = new ArrayList(); diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java new file mode 100644 index 0000000000..5bd23a282a --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java @@ -0,0 +1,339 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs; + +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.irs.WiremockSupport.createEndpointDataReference; +import static org.eclipse.tractusx.irs.WiremockSupport.randomUUID; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_URL; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.EDC_DISCOVERY_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.TEST_BPN; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postDiscoveryFinder200; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postDiscoveryFinder404; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postEdcDiscoveryEmpty200; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.LOOKUP_SHELLS_TEMPLATE; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.PUBLIC_LOOKUP_SHELLS_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.PUBLIC_SHELL_DESCRIPTORS_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.SHELL_DESCRIPTORS_TEMPLATE; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getLookupShells200; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getShellDescriptor200; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_CATALOG; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_NEGOTIATE; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_STATE; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_TRANSFER; + +import java.time.Duration; +import java.util.List; +import java.util.Objects; + +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import org.awaitility.Awaitility; +import org.eclipse.tractusx.irs.bpdm.BpdmWireMockSupport; +import org.eclipse.tractusx.irs.component.JobHandle; +import org.eclipse.tractusx.irs.component.Jobs; +import org.eclipse.tractusx.irs.component.RegisterJob; +import org.eclipse.tractusx.irs.component.enums.JobState; +import org.eclipse.tractusx.irs.data.StringMapper; +import org.eclipse.tractusx.irs.edc.client.EndpointDataReferenceStorage; +import org.eclipse.tractusx.irs.semanticshub.AspectModels; +import org.eclipse.tractusx.irs.semanticshub.SemanticHubWireMockSupport; +import org.eclipse.tractusx.irs.services.IrsItemGraphQueryService; +import org.eclipse.tractusx.irs.services.SemanticHubService; +import org.eclipse.tractusx.irs.services.validation.SchemaNotFoundException; +import org.eclipse.tractusx.irs.testing.containers.MinioContainer; +import org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport; +import org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cache.CacheManager; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.support.TestPropertySourceUtils; +import org.testcontainers.junit.jupiter.Testcontainers; + +@WireMockTest(httpPort = 8085) +@Testcontainers +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = WireMockTestConfig.class) +@ContextConfiguration(initializers = IrsWireMockIntegrationTest.MinioConfigInitializer.class) +@ActiveProfiles("integrationtest") +class IrsWireMockIntegrationTest { + public static final String SEMANTIC_HUB_URL = "http://semantic.hub/models"; + public static final String EDC_URL = "http://edc.test"; + private static final String ACCESS_KEY = "accessKey"; + private static final String SECRET_KEY = "secretKey"; + private static final MinioContainer minioContainer = new MinioContainer( + new MinioContainer.CredentialsProvider(ACCESS_KEY, SECRET_KEY)).withReuse(true); + @Autowired + private IrsItemGraphQueryService irsService; + + @Autowired + private SemanticHubService semanticHubService; + @Autowired + private EndpointDataReferenceStorage endpointDataReferenceStorage; + @Autowired + private CacheManager cacheManager; + + @BeforeAll + static void startContainer() { + minioContainer.start(); + WiremockSupport.successfulSemanticModelRequest(); + } + + @AfterEach + void tearDown() { + cacheManager.getCacheNames() + .forEach(cacheName -> Objects.requireNonNull(cacheManager.getCache(cacheName)).clear()); + } + + @AfterAll + static void stopContainer() { + minioContainer.stop(); + } + + @DynamicPropertySource + static void configureProperties(DynamicPropertyRegistry registry) { + registry.add("bpdm.bpnEndpoint", () -> BpdmWireMockSupport.BPDM_URL_TEMPLATE); + registry.add("digitalTwinRegistry.discoveryFinderUrl", () -> DISCOVERY_FINDER_URL); + registry.add("digitalTwinRegistry.shellDescriptorTemplate", () -> SHELL_DESCRIPTORS_TEMPLATE); + registry.add("digitalTwinRegistry.lookupShellsTemplate", () -> LOOKUP_SHELLS_TEMPLATE); + registry.add("digitalTwinRegistry.type", () -> "decentral"); + registry.add("semanticshub.url", () -> SEMANTIC_HUB_URL); + registry.add("semanticshub.modelJsonSchemaEndpoint", () -> SemanticHubWireMockSupport.SEMANTIC_HUB_SCHEMA_URL); + registry.add("semanticshub.defaultUrns", () -> ""); + registry.add("irs-edc-client.controlplane.endpoint.data", () -> EDC_URL); + registry.add("irs-edc-client.controlplane.endpoint.catalog", () -> PATH_CATALOG); + registry.add("irs-edc-client.controlplane.endpoint.contract-negotiation", () -> PATH_NEGOTIATE); + registry.add("irs-edc-client.controlplane.endpoint.transfer-process", () -> PATH_TRANSFER); + registry.add("irs-edc-client.controlplane.endpoint.state-suffix", () -> PATH_STATE); + registry.add("irs-edc-client.controlplane.api-key.header", () -> "X-Api-Key"); + registry.add("irs-edc-client.controlplane.api-key.secret", () -> "test"); + registry.add("resilience4j.retry.configs.default.waitDuration", () -> "1s"); + } + + @Test + void shouldStartApplicationAndCollectSemanticModels() throws SchemaNotFoundException { + // Arrange + WiremockSupport.successfulSemanticModelRequest(); + + // Act + final AspectModels allAspectModels = semanticHubService.getAllAspectModels(); + + // Assert + assertThat(allAspectModels.models()).hasSize(78); + } + + @Test + void shouldStopJobAfterDepthIsReached() { + // Arrange + final String globalAssetIdLevel1 = "globalAssetId"; + final String globalAssetIdLevel2 = "urn:uuid:6d505432-8b31-4966-9514-4b753372683f"; + + WiremockSupport.successfulSemanticModelRequest(); + WiremockSupport.successfulSemanticHubRequests(); + WiremockSupport.successfulDiscovery(); + + successfulRegistryAndDataRequest(globalAssetIdLevel1, "Cathode", TEST_BPN, "integrationtesting/batch-1.json", + "integrationtesting/singleLevelBomAsBuilt-1.json"); + successfulRegistryAndDataRequest(globalAssetIdLevel2, "Polyamid", TEST_BPN, "integrationtesting/batch-2.json", + "integrationtesting/singleLevelBomAsBuilt-2.json"); + + BpdmWireMockSupport.bpdmWillReturnCompanyName(DiscoveryServiceWiremockSupport.TEST_BPN, "Company Name"); + + final RegisterJob request = WiremockSupport.jobRequest(globalAssetIdLevel1, TEST_BPN, 1); + + // Act + final JobHandle jobHandle = irsService.registerItemJob(request); + assertThat(jobHandle.getId()).isNotNull(); + waitForCompletion(jobHandle); + + Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), true); + + // Assert + WiremockSupport.verifyDiscoveryCalls(1); + WiremockSupport.verifyNegotiationCalls(3); + + assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).hasSize(2); + assertThat(jobForJobId.getRelationships()).hasSize(1); + assertThat(jobForJobId.getTombstones()).isEmpty(); + } + + @Test + void shouldCreateTombstoneWhenDiscoveryServiceNotAvailable() { + // Arrange + WiremockSupport.successfulSemanticModelRequest(); + stubFor(postDiscoveryFinder404()); + final String globalAssetId = "globalAssetId"; + final RegisterJob request = WiremockSupport.jobRequest(globalAssetId, TEST_BPN, 1); + + // Act + final JobHandle jobHandle = irsService.registerItemJob(request); + + // Assert + assertThat(jobHandle.getId()).isNotNull(); + waitForCompletion(jobHandle); + final Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), true); + + verify(1, postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(0, postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + + assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).isEmpty(); + assertThat(jobForJobId.getRelationships()).isEmpty(); + assertThat(jobForJobId.getTombstones()).hasSize(1); + } + + @Test + void shouldCreateTombstoneWhenEdcDiscoveryIsEmpty() { + // Arrange + WiremockSupport.successfulSemanticModelRequest(); + stubFor(postDiscoveryFinder200()); + stubFor(postEdcDiscoveryEmpty200()); + final String globalAssetId = "globalAssetId"; + final RegisterJob request = WiremockSupport.jobRequest(globalAssetId, TEST_BPN, 1); + + // Act + final JobHandle jobHandle = irsService.registerItemJob(request); + + // Assert + assertThat(jobHandle.getId()).isNotNull(); + waitForCompletion(jobHandle); + final Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), true); + + WiremockSupport.verifyDiscoveryCalls(1); + + assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).isEmpty(); + assertThat(jobForJobId.getRelationships()).isEmpty(); + assertThat(jobForJobId.getTombstones()).hasSize(1); + } + + @Test + void shouldStartRecursiveProcesses() { + // Arrange + final String globalAssetIdLevel1 = "urn:uuid:334cce52-1f52-4bc9-9dd1-410bbe497bbc"; + final String globalAssetIdLevel2 = "urn:uuid:7e4541ea-bb0f-464c-8cb3-021abccbfaf5"; + final String globalAssetIdLevel3 = "urn:uuid:a314ad6b-77ea-417e-ae2d-193b3e249e99"; + + WiremockSupport.successfulSemanticModelRequest(); + WiremockSupport.successfulSemanticHubRequests(); + WiremockSupport.successfulDiscovery(); + + successfulRegistryAndDataRequest(globalAssetIdLevel1, "Cathode", TEST_BPN, "integrationtesting/batch-1.json", + "integrationtesting/singleLevelBomAsBuilt-1.json"); + successfulRegistryAndDataRequest(globalAssetIdLevel2, "Polyamid", TEST_BPN, "integrationtesting/batch-2.json", + "integrationtesting/singleLevelBomAsBuilt-2.json"); + successfulRegistryAndDataRequest(globalAssetIdLevel3, "GenericChemical", TEST_BPN, + "integrationtesting/batch-3.json", "integrationtesting/singleLevelBomAsBuilt-3.json"); + + BpdmWireMockSupport.bpdmWillReturnCompanyName(DiscoveryServiceWiremockSupport.TEST_BPN, "Company Name"); + + final RegisterJob request = WiremockSupport.jobRequest(globalAssetIdLevel1, TEST_BPN, 4); + + // Act + final JobHandle jobHandle = irsService.registerItemJob(request); + + // Assert + assertThat(jobHandle.getId()).isNotNull(); + waitForCompletion(jobHandle); + final Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), false); + System.out.println(StringMapper.mapToString(jobForJobId)); + + assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).hasSize(3); + assertThat(jobForJobId.getRelationships()).hasSize(2); + assertThat(jobForJobId.getTombstones()).isEmpty(); + assertThat(jobForJobId.getSubmodels()).hasSize(6); + + WiremockSupport.verifyDiscoveryCalls(1); + WiremockSupport.verifyNegotiationCalls(6); + } + + private void successfulRegistryAndDataRequest(final String globalAssetId, final String idShort, final String bpn, + final String batchFileName, final String sbomFileName) { + + final String edcAssetId = WiremockSupport.randomUUIDwithPrefix(); + final String batch = WiremockSupport.submodelRequest(edcAssetId, "Batch", + "urn:samm:io.catenax.batch:2.0.0#Batch", batchFileName); + + final String singleLevelBomAsBuilt = WiremockSupport.submodelRequest(edcAssetId, "SingleLevelBomAsBuilt", + "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt", sbomFileName); + + successfulNegotiation(edcAssetId); + final List submodelDescriptors = List.of(batch, singleLevelBomAsBuilt); + + final String shellId = WiremockSupport.randomUUIDwithPrefix(); + final String registryEdcAssetId = "registry-asset"; + successfulNegotiation(registryEdcAssetId); + stubFor(getLookupShells200(PUBLIC_LOOKUP_SHELLS_PATH, List.of(shellId)).withQueryParam("assetIds", + containing(globalAssetId))); + stubFor(getShellDescriptor200(PUBLIC_SHELL_DESCRIPTORS_PATH + WiremockSupport.encodedId(shellId), bpn, + submodelDescriptors, globalAssetId, shellId, idShort)); + } + + private void successfulNegotiation(final String edcAssetId) { + final String negotiationId = randomUUID(); + final String transferProcessId = randomUUID(); + final String contractAgreementId = "%s:%s:%s".formatted(randomUUID(), edcAssetId, randomUUID()); + SubmodelFacadeWiremockSupport.prepareNegotiation(negotiationId, transferProcessId, contractAgreementId, + edcAssetId); + endpointDataReferenceStorage.put(contractAgreementId, createEndpointDataReference(contractAgreementId)); + } + + private void waitForCompletion(final JobHandle jobHandle) { + Awaitility.await() + .timeout(Duration.ofSeconds(35)) + .pollInterval(Duration.ofSeconds(1)) + .until(() -> irsService.getJobForJobId(jobHandle.getId(), false) + .getJob() + .getState() + .equals(JobState.COMPLETED)); + } + + public static class MinioConfigInitializer + implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + final String hostAddress = minioContainer.getHostAddress(); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext, + "blobstore.endpoint=http://" + hostAddress, "blobstore.accessKey=" + ACCESS_KEY, + "blobstore.secretKey=" + SECRET_KEY, "policystore.persistence.endpoint=http://" + hostAddress, + "policystore.persistence.accessKey=" + ACCESS_KEY, + "policystore.persistence.secretKey=" + SECRET_KEY, + "policystore.persistence.bucketName=policy-test"); + } + } + +} diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/WireMockTestConfig.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/WireMockTestConfig.java new file mode 100644 index 0000000000..8c41604857 --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/WireMockTestConfig.java @@ -0,0 +1,100 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs; + +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.BPDM_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.DISCOVERY_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.DTR_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.EDC_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.NO_ERROR_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.SEMHUB_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy; + +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.policy.model.PolicyRegistrationTypes; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +@TestConfiguration +public class WireMockTestConfig { + public static final int HTTP_PORT = 8085; + private static final String PROXY_SERVER_HOST = "127.0.0.1"; + + @Primary + @Profile("integrationtest") + @Bean(DTR_REST_TEMPLATE) + RestTemplate dtrRestTemplate() { + return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + } + + @Primary + @Profile("integrationtest") + @Bean(EDC_REST_TEMPLATE) + RestTemplate edcRestTemplate() { + final RestTemplate edcRestTemplate = restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + final List> messageConverters = edcRestTemplate.getMessageConverters(); + for (final HttpMessageConverter converter : messageConverters) { + if (converter instanceof final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) { + final ObjectMapper mappingJackson2HttpMessageConverterObjectMapper = mappingJackson2HttpMessageConverter.getObjectMapper(); + PolicyRegistrationTypes.TYPES.forEach( + mappingJackson2HttpMessageConverterObjectMapper::registerSubtypes); + } + } + return edcRestTemplate; + } + + @Primary + @Profile("integrationtest") + @Bean(NO_ERROR_REST_TEMPLATE) + RestTemplate noErrorRestTemplate() { + return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + } + + @Primary + @Profile("integrationtest") + @Bean(DISCOVERY_REST_TEMPLATE) + RestTemplate discoveryRestTemplate() { + return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + } + + @Primary + @Profile("integrationtest") + @Bean(BPDM_REST_TEMPLATE) + RestTemplate bpdmRestTemplate() { + return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + } + + @Primary + @Profile("integrationtest") + @Bean(SEMHUB_REST_TEMPLATE) + @Qualifier(SEMHUB_REST_TEMPLATE) + RestTemplate semanticHubRestTemplate() { + return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + } + +} diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/WiremockSupport.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/WiremockSupport.java new file mode 100644 index 0000000000..9698272e7c --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/WiremockSupport.java @@ -0,0 +1,142 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.apache.commons.codec.binary.Base64.encodeBase64String; +import static org.eclipse.tractusx.irs.semanticshub.SemanticHubWireMockSupport.semanticHubWillReturnAllModels; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.DATAPLANE_PUBLIC_URL; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.submodelDescriptor; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; +import org.eclipse.tractusx.irs.component.RegisterJob; +import org.eclipse.tractusx.irs.component.enums.Direction; +import org.eclipse.tractusx.irs.data.StringMapper; +import org.eclipse.tractusx.irs.edc.client.configuration.JsonLdConfiguration; +import org.eclipse.tractusx.irs.edc.client.model.EDRAuthCode; +import org.eclipse.tractusx.irs.semanticshub.SemanticHubWireMockSupport; +import org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport; +import org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport; +import org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport; + +public class WiremockSupport { + public static EndpointDataReference createEndpointDataReference(final String contractAgreementId) { + final EDRAuthCode edrAuthCode = EDRAuthCode.builder() + .cid(contractAgreementId) + .dad("test") + .exp(9999999999L) + .build(); + final String b64EncodedAuthCode = Base64.getUrlEncoder() + .encodeToString(StringMapper.mapToString(edrAuthCode) + .getBytes(StandardCharsets.UTF_8)); + final String jwtToken = "eyJhbGciOiJSUzI1NiJ9." + b64EncodedAuthCode + ".test"; + return EndpointDataReference.Builder.newInstance() + .authKey("testkey") + .authCode(jwtToken) + .properties( + Map.of(JsonLdConfiguration.NAMESPACE_EDC_CID, contractAgreementId)) + .endpoint(SubmodelFacadeWiremockSupport.DATAPLANE_HOST + + SubmodelFacadeWiremockSupport.PATH_DATAPLANE_PUBLIC) + .build(); + } + + static void successfulSemanticModelRequest() { + semanticHubWillReturnAllModels("semantichub/all-models-page-IT.json"); + } + + static RegisterJob jobRequest(final String globalAssetId, final String bpn, final int depth) { + return RegisterJob.builder() + .key(PartChainIdentificationKey.builder().bpn(bpn).globalAssetId(globalAssetId).build()) + .depth(depth) + .aspects(List.of("Batch", "SingleLevelBomAsBuilt")) + .collectAspects(true) + .lookupBPNs(true) + .direction(Direction.DOWNWARD) + .build(); + } + + static void successfulDiscovery() { + stubFor(DiscoveryServiceWiremockSupport.postDiscoveryFinder200()); + stubFor(DiscoveryServiceWiremockSupport.postEdcDiscovery200()); + } + + static String encodedId(final String shellId) { + return encodeBase64String(shellId.getBytes(StandardCharsets.UTF_8)); + } + + static void verifyDiscoveryCalls(final int times) { + verify(times, postRequestedFor(urlPathEqualTo(DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_PATH))); + verify(times, postRequestedFor(urlPathEqualTo(DiscoveryServiceWiremockSupport.EDC_DISCOVERY_PATH))); + } + + static void verifyNegotiationCalls(final int times) { + verify(times, postRequestedFor(urlPathEqualTo(SubmodelFacadeWiremockSupport.PATH_NEGOTIATE))); + verify(times, postRequestedFor(urlPathEqualTo(SubmodelFacadeWiremockSupport.PATH_CATALOG))); + verify(times * 2, getRequestedFor(urlPathMatching(SubmodelFacadeWiremockSupport.PATH_NEGOTIATE + "/.*"))); + verify(times, getRequestedFor(urlPathMatching( + SubmodelFacadeWiremockSupport.PATH_NEGOTIATE + "/.*" + SubmodelFacadeWiremockSupport.PATH_STATE))); + verify(times, postRequestedFor(urlPathEqualTo(SubmodelFacadeWiremockSupport.PATH_TRANSFER))); + verify(times * 2, getRequestedFor(urlPathMatching(SubmodelFacadeWiremockSupport.PATH_TRANSFER + "/.*"))); + verify(times, getRequestedFor(urlPathMatching( + SubmodelFacadeWiremockSupport.PATH_TRANSFER + "/.*" + SubmodelFacadeWiremockSupport.PATH_STATE))); + } + + static void successfulDataRequests(final String assetId, final String fileName) { + stubFor(get(urlPathMatching(DtrWiremockSupport.DATAPLANE_PUBLIC_PATH + "/" + assetId)).willReturn( + responseWithStatus(200).withBodyFile(fileName))); + } + + static void successfulSemanticHubRequests() { + SemanticHubWireMockSupport.semanticHubWillReturnBatchSchema(); + SemanticHubWireMockSupport.semanticHubWillReturnSingleLevelBomAsBuiltSchema(); + } + + static String randomUUIDwithPrefix() { + final String uuidPrefix = "urn:uuid:"; + return uuidPrefix + randomUUID(); + } + + static String randomUUID() { + return UUID.randomUUID().toString(); + } + + static String submodelRequest(final String edcAssetId, final String SingleLevelBomAsBuilt, final String semanticId, + final String sbomFileName) { + final String sbomId = randomUUIDwithPrefix(); + final String submoodel = submodelDescriptor(DATAPLANE_PUBLIC_URL, edcAssetId, + DiscoveryServiceWiremockSupport.CONTROLPLANE_PUBLIC_URL, SingleLevelBomAsBuilt, sbomId, semanticId); + successfulDataRequests(sbomId, sbomFileName); + return submoodel; + } +} \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegateTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegateTest.java index fea1b5f30a..f38685a3a8 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegateTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegateTest.java @@ -37,6 +37,7 @@ import org.eclipse.tractusx.irs.component.assetadministrationshell.ProtocolInformation; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelFacade; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor; import org.eclipse.tractusx.irs.registryclient.discovery.ConnectorEndpointsService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -61,7 +62,7 @@ void setUp() { @Test void shouldUseDspEndpointIfPresent() throws EdcClientException { // Arrange - when(submodelFacade.getSubmodelRawPayload(any(), any(), any())).thenReturn("test"); + when(submodelFacade.getSubmodelPayload(any(), any(), any())).thenReturn(new SubmodelDescriptor("cid", "test")); final Endpoint endpoint = Endpoint.builder() .protocolInformation(ProtocolInformation.builder() .href("http://dataplane.test/123") @@ -72,11 +73,11 @@ void shouldUseDspEndpointIfPresent() throws EdcClientException { final String bpn = "BPN123"; // Act - final String submodel = submodelDelegate.requestSubmodelAsString(submodelFacade, null, endpoint, bpn); + final String submodel = submodelDelegate.requestSubmodel(submodelFacade, null, endpoint, bpn).getPayload(); // Assert assertThat(submodel).isEqualTo("test"); - verify(submodelFacade, times(1)).getSubmodelRawPayload("http://edc.test", "http://dataplane.test/123", "123"); + verify(submodelFacade, times(1)).getSubmodelPayload("http://edc.test", "http://dataplane.test/123", "123"); } @Test @@ -84,9 +85,9 @@ void shouldUseDiscoveryFinderIfDspEndpointNotPresent() throws EdcClientException // Arrange final String connector1 = "http://edc.test1"; final String connector2 = "http://edc.test2"; - when(submodelFacade.getSubmodelRawPayload(eq(connector1), any(), any())).thenThrow( + when(submodelFacade.getSubmodelPayload(eq(connector1), any(), any())).thenThrow( new EdcClientException("test")); - when(submodelFacade.getSubmodelRawPayload(eq(connector2), any(), any())).thenReturn("test"); + when(submodelFacade.getSubmodelPayload(eq(connector2), any(), any())).thenReturn(new SubmodelDescriptor("cid", "test")); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of(connector1, connector2)); final String dataplaneUrl = "http://dataplane.test/123"; final Endpoint endpoint = Endpoint.builder() @@ -98,13 +99,13 @@ void shouldUseDiscoveryFinderIfDspEndpointNotPresent() throws EdcClientException final String bpn = "BPN123"; // Act - final String submodel = submodelDelegate.requestSubmodelAsString(submodelFacade, connectorEndpointsService, - endpoint, bpn); + final String submodel = submodelDelegate.requestSubmodel(submodelFacade, connectorEndpointsService, + endpoint, bpn).getPayload(); // Assert assertThat(submodel).isEqualTo("test"); - verify(submodelFacade, times(1)).getSubmodelRawPayload(connector1, dataplaneUrl, "123"); - verify(submodelFacade, times(1)).getSubmodelRawPayload(connector2, dataplaneUrl, "123"); + verify(submodelFacade, times(1)).getSubmodelPayload(connector1, dataplaneUrl, "123"); + verify(submodelFacade, times(1)).getSubmodelPayload(connector2, dataplaneUrl, "123"); verify(connectorEndpointsService, times(1)).fetchConnectorEndpoints(bpn); } @@ -113,7 +114,7 @@ void shouldThrowGenericEdcClientExceptionIfAllEndpointsThrowExceptions() throws // Arrange final String connector1 = "http://edc.test1"; final String connector2 = "http://edc.test2"; - when(submodelFacade.getSubmodelRawPayload(any(), any(), any())).thenThrow(new EdcClientException("test")); + when(submodelFacade.getSubmodelPayload(any(), any(), any())).thenThrow(new EdcClientException("test")); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of(connector1, connector2)); final String dataplaneUrl = "http://dataplane.test/123"; final Endpoint endpoint = Endpoint.builder() @@ -126,12 +127,12 @@ void shouldThrowGenericEdcClientExceptionIfAllEndpointsThrowExceptions() throws // Act assertThatExceptionOfType(EdcClientException.class).isThrownBy( - () -> submodelDelegate.requestSubmodelAsString(submodelFacade, connectorEndpointsService, endpoint, + () -> submodelDelegate.requestSubmodel(submodelFacade, connectorEndpointsService, endpoint, bpn)); // Assert - verify(submodelFacade, times(1)).getSubmodelRawPayload(connector1, dataplaneUrl, "123"); - verify(submodelFacade, times(1)).getSubmodelRawPayload(connector2, dataplaneUrl, "123"); + verify(submodelFacade, times(1)).getSubmodelPayload(connector1, dataplaneUrl, "123"); + verify(submodelFacade, times(1)).getSubmodelPayload(connector2, dataplaneUrl, "123"); verify(connectorEndpointsService, times(1)).fetchConnectorEndpoints(bpn); } } \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegateTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegateTest.java index 1530905038..7a33d5c224 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegateTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegateTest.java @@ -25,6 +25,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.irs.util.TestMother.jobParameter; +import static org.eclipse.tractusx.irs.util.TestMother.jobParameterAuditContractNegotiation; +import static org.eclipse.tractusx.irs.util.TestMother.shell; import static org.eclipse.tractusx.irs.util.TestMother.shellDescriptor; import static org.eclipse.tractusx.irs.util.TestMother.submodelDescriptorWithoutHref; import static org.mockito.ArgumentMatchers.any; @@ -53,7 +55,7 @@ class DigitalTwinDelegateTest { void shouldFillItemContainerWithShell() throws RegistryServiceException { // given when(digitalTwinRegistryService.fetchShells(any())).thenReturn( - List.of(shellDescriptor(List.of(submodelDescriptorWithoutHref("any"))))); + List.of(shell("", shellDescriptor(List.of(submodelDescriptorWithoutHref("any")))))); // when final ItemContainer result = digitalTwinDelegate.process(ItemContainer.builder(), jobParameter(), @@ -62,14 +64,32 @@ void shouldFillItemContainerWithShell() throws RegistryServiceException { // then assertThat(result).isNotNull(); assertThat(result.getShells()).isNotEmpty(); - assertThat(result.getShells().get(0).getSubmodelDescriptors()).isNotEmpty(); + assertThat(result.getShells().get(0).payload().getSubmodelDescriptors()).isNotEmpty(); + assertThat(result.getShells().get(0).contractAgreementId()).isNull(); + } + + @Test + void shouldFillItemContainerWithShellAndContractAgreementIdWhenAuditFlag() throws RegistryServiceException { + // given + when(digitalTwinRegistryService.fetchShells(any())).thenReturn( + List.of(shell("", shellDescriptor(List.of(submodelDescriptorWithoutHref("any")))))); + + // when + final ItemContainer result = digitalTwinDelegate.process(ItemContainer.builder(), jobParameterAuditContractNegotiation(), + new AASTransferProcess("id", 0), createKey()); + + // then + assertThat(result).isNotNull(); + assertThat(result.getShells()).isNotEmpty(); + assertThat(result.getShells().get(0).payload().getSubmodelDescriptors()).isNotEmpty(); + assertThat(result.getShells().get(0).contractAgreementId()).isNotNull(); } @Test void shouldFillItemContainerWithShellAndFilteredSubmodelDescriptorsWhenDepthReached() throws RegistryServiceException { // given when(digitalTwinRegistryService.fetchShells(any())).thenReturn( - List.of(shellDescriptor(List.of(submodelDescriptorWithoutHref("any"))))); + List.of(shell("", shellDescriptor(List.of(submodelDescriptorWithoutHref("any")))))); final JobParameter jobParameter = JobParameter.builder().depth(1).aspects(List.of()).build(); // when @@ -79,7 +99,7 @@ void shouldFillItemContainerWithShellAndFilteredSubmodelDescriptorsWhenDepthReac // then assertThat(result).isNotNull(); assertThat(result.getShells()).isNotEmpty(); - assertThat(result.getShells().get(0).getSubmodelDescriptors()).isEmpty(); + assertThat(result.getShells().get(0).payload().getSubmodelDescriptors()).isEmpty(); } @Test diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegateTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegateTest.java index bf9443a765..3b060bd29e 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegateTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegateTest.java @@ -25,7 +25,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.irs.util.TestMother.jobParameter; +import static org.eclipse.tractusx.irs.util.TestMother.jobParameterCollectAspects; import static org.eclipse.tractusx.irs.util.TestMother.jobParameterUpward; +import static org.eclipse.tractusx.irs.util.TestMother.shell; import static org.eclipse.tractusx.irs.util.TestMother.shellDescriptor; import static org.eclipse.tractusx.irs.util.TestMother.submodelDescriptorWithDspEndpoint; import static org.mockito.ArgumentMatchers.any; @@ -46,6 +48,8 @@ import org.eclipse.tractusx.irs.component.enums.ProcessStep; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelFacade; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.edc.client.exceptions.UsagePolicyException; +import org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor; import org.eclipse.tractusx.irs.registryclient.discovery.ConnectorEndpointsService; import org.eclipse.tractusx.irs.util.JsonUtil; import org.junit.jupiter.api.Test; @@ -61,23 +65,20 @@ class RelationshipDelegateTest { final String singleLevelBomAsBuiltAspectName = "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt"; final String singleLevelUsageAsBuiltAspectName = "urn:bamm:io.catenax.single_level_usage_as_built:2.0.0#SingleLevelUsageAsBuilt"; - private static PartChainIdentificationKey createKey() { - return PartChainIdentificationKey.builder().globalAssetId("itemId").bpn("bpn123").build(); - } - @Test void shouldFillItemContainerWithRelationshipAndAddChildIdsToProcess() throws EdcClientException, URISyntaxException, IOException { // given - when(submodelFacade.getSubmodelRawPayload(anyString(), anyString(), anyString())).thenReturn(Files.readString( - Paths.get(Objects.requireNonNull(getClass().getResource("/singleLevelBomAsBuilt.json")).toURI()))); + final String payload = Files.readString( + Paths.get(Objects.requireNonNull(getClass().getResource("/singleLevelBomAsBuilt.json")).toURI())); + when(submodelFacade.getSubmodelPayload(anyString(), anyString(), anyString())).thenReturn(new SubmodelDescriptor("cid", payload)); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( singleLevelBomAsBuiltAspectName, - "address")))); + "address"))))); final AASTransferProcess aasTransferProcess = new AASTransferProcess(); // when @@ -95,15 +96,16 @@ void shouldFillItemContainerWithRelationshipAndAddChildIdsToProcess() void shouldFillItemContainerWithUpwardRelationshipAndAddChildIdsToProcess() throws EdcClientException, URISyntaxException, IOException { // given - when(submodelFacade.getSubmodelRawPayload(anyString(), anyString(), anyString())).thenReturn(Files.readString( - Paths.get(Objects.requireNonNull(getClass().getResource("/singleLevelUsageAsBuilt.json")).toURI()))); + final String payload = Files.readString( + Paths.get(Objects.requireNonNull(getClass().getResource("/singleLevelUsageAsBuilt.json")).toURI())); + when(submodelFacade.getSubmodelPayload(anyString(), anyString(), anyString())).thenReturn(new SubmodelDescriptor("cid", payload)); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( singleLevelUsageAsBuiltAspectName, - "address")))); + "address"))))); final AASTransferProcess aasTransferProcess = new AASTransferProcess(); // when @@ -121,10 +123,10 @@ void shouldFillItemContainerWithUpwardRelationshipAndAddChildIdsToProcess() @Test void shouldPutTombstoneForMissingBpn() { final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( singleLevelBomAsBuiltAspectName, - "address")))); + "address"))))); // when final ItemContainer result = relationshipDelegate.process(itemContainerWithShell, jobParameter(), new AASTransferProcess(), PartChainIdentificationKey.builder().globalAssetId("testId").build()); @@ -140,15 +142,15 @@ void shouldPutTombstoneForMissingBpn() { @Test void shouldCatchRestClientExceptionAndPutTombstone() throws EdcClientException { // given - when(submodelFacade.getSubmodelRawPayload(anyString(), anyString(), anyString())).thenThrow( + when(submodelFacade.getSubmodelPayload(anyString(), anyString(), anyString())).thenThrow( new EdcClientException("Unable to call endpoint")); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( singleLevelBomAsBuiltAspectName, - "address")))); + "address"))))); // when final ItemContainer result = relationshipDelegate.process(itemContainerWithShell, jobParameter(), @@ -165,14 +167,14 @@ void shouldCatchRestClientExceptionAndPutTombstone() throws EdcClientException { @Test void shouldCatchJsonParseExceptionAndPutTombstone() throws EdcClientException { // given - when(submodelFacade.getSubmodelRawPayload(anyString(), anyString(), anyString())).thenThrow( + when(submodelFacade.getSubmodelPayload(anyString(), anyString(), anyString())).thenThrow( new EdcClientException(new Exception("Payload did not match expected submodel"))); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( singleLevelBomAsBuiltAspectName, - "address")))); + "address"))))); // when final ItemContainer result = relationshipDelegate.process(itemContainerWithShell, jobParameter(), @@ -186,4 +188,31 @@ void shouldCatchJsonParseExceptionAndPutTombstone() throws EdcClientException { ProcessStep.SUBMODEL_REQUEST); } + @Test + void shouldCatchUsagePolicyExceptionAndPutTombstone() throws EdcClientException { + // given + final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + singleLevelBomAsBuiltAspectName, + "address"))))); + + // when + when(submodelFacade.getSubmodelPayload(any(), any(), any())).thenThrow(new UsagePolicyException("itemId", null)); + when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("connector.endpoint.nl")); + final ItemContainer result = relationshipDelegate.process(itemContainerWithShell, jobParameter(), + new AASTransferProcess(), createKey()); + + // then + assertThat(result).isNotNull(); + assertThat(result.getTombstones()).hasSize(1); + assertThat(result.getTombstones().get(0).getCatenaXId()).isEqualTo("itemId"); + assertThat(result.getTombstones().get(0).getProcessingError().getProcessStep()).isEqualTo( + ProcessStep.USAGE_POLICY_VALIDATION); + } + + private static PartChainIdentificationKey createKey() { + return PartChainIdentificationKey.builder().globalAssetId("itemId").bpn("bpn123").build(); + } + } diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegateTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegateTest.java index 8997f69352..312113d974 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegateTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegateTest.java @@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.irs.util.TestMother.jobParameterCollectAspects; import static org.eclipse.tractusx.irs.util.TestMother.jobParameterFilter; +import static org.eclipse.tractusx.irs.util.TestMother.shell; import static org.eclipse.tractusx.irs.util.TestMother.shellDescriptor; import static org.eclipse.tractusx.irs.util.TestMother.submodelDescriptor; import static org.eclipse.tractusx.irs.util.TestMother.submodelDescriptorWithDspEndpoint; @@ -44,6 +45,7 @@ import org.eclipse.tractusx.irs.edc.client.ItemNotFoundInCatalogException; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; import org.eclipse.tractusx.irs.edc.client.exceptions.UsagePolicyException; +import org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor; import org.eclipse.tractusx.irs.registryclient.discovery.ConnectorEndpointsService; import org.eclipse.tractusx.irs.semanticshub.SemanticsHubFacade; import org.eclipse.tractusx.irs.services.validation.InvalidSchemaException; @@ -63,21 +65,17 @@ class SubmodelDelegateTest { final SubmodelDelegate submodelDelegate = new SubmodelDelegate(submodelFacade, semanticsHubFacade, jsonValidatorService, new JsonUtil(), connectorEndpointsService); - private static PartChainIdentificationKey createKey() { - return PartChainIdentificationKey.builder().globalAssetId("itemId").bpn("bpn123").build(); - } - @Test void shouldFilterSubmodelDescriptorsByAspectTypeFilter() { // given final ItemContainer.ItemContainerBuilder itemContainerShellWithTwoSubmodels = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( "urn:bamm:com.catenax.serial_part_typization:1.0.0#SerialPartTypization", "testSerialPartTypizationEndpoint"), submodelDescriptorWithDspEndpoint( "urn:bamm:com.catenax.assembly_part_relationship:1.0.0#AssemblyPartRelationship", - "testAssemblyPartRelationshipEndpoint")))); + "testAssemblyPartRelationshipEndpoint"))))); // when final ItemContainer result = submodelDelegate.process(itemContainerShellWithTwoSubmodels, jobParameterFilter(), @@ -85,20 +83,20 @@ void shouldFilterSubmodelDescriptorsByAspectTypeFilter() { // then assertThat(result).isNotNull(); - assertThat(result.getShells().get(0).getSubmodelDescriptors()).isEmpty(); + assertThat(result.getShells().get(0).payload().getSubmodelDescriptors()).isEmpty(); } @Test void shouldCatchJsonParseExceptionAndPutTombstone() throws SchemaNotFoundException { // given final ItemContainer.ItemContainerBuilder itemContainerShellWithTwoSubmodels = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart", "testSerialPartEndpoint"), submodelDescriptorWithDspEndpoint( "urn:bamm:com.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", - "testSingleLevelBomAsBuiltEndpoint")))); + "testSingleLevelBomAsBuiltEndpoint"))))); // when when(semanticsHubFacade.getModelJsonSchema(any())).thenThrow( @@ -117,13 +115,13 @@ void shouldCatchJsonParseExceptionAndPutTombstone() throws SchemaNotFoundExcepti @Test void shouldPutTombstoneForMissingBpn() { final ItemContainer.ItemContainerBuilder itemContainerShellWithTwoSubmodels = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart", "testSerialPartEndpoint"), submodelDescriptorWithDspEndpoint( "urn:bamm:com.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", - "testSingleLevelBomAsBuiltEndpoint")))); + "testSingleLevelBomAsBuiltEndpoint"))))); // when final ItemContainer result = submodelDelegate.process(itemContainerShellWithTwoSubmodels, @@ -142,16 +140,16 @@ void shouldPutTombstoneForMissingBpn() { void shouldCatchUsagePolicyExceptionAndPutTombstone() throws EdcClientException { // given final ItemContainer.ItemContainerBuilder itemContainerShellWithTwoSubmodels = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart", "testSerialPartEndpoint"), submodelDescriptorWithDspEndpoint( "urn:bamm:com.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", - "testSingleLevelBomAsBuiltEndpoint")))); + "testSingleLevelBomAsBuiltEndpoint"))))); // when - when(submodelFacade.getSubmodelRawPayload(any(), any(), any())).thenThrow(new UsagePolicyException("itemId")); + when(submodelFacade.getSubmodelPayload(any(), any(), any())).thenThrow(new UsagePolicyException("itemId", null)); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("connector.endpoint.nl")); final ItemContainer result = submodelDelegate.process(itemContainerShellWithTwoSubmodels, jobParameterCollectAspects(), new AASTransferProcess(), createKey()); @@ -168,17 +166,17 @@ void shouldCatchUsagePolicyExceptionAndPutTombstone() throws EdcClientException void shouldRequestForAllEndpoints() throws EdcClientException, InvalidSchemaException { // given final ItemContainer.ItemContainerBuilder itemContainerShellWithOneSubmodel = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptor( "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart", "testSerialPartEndpoint", - "")))); + ""))))); // when - when(submodelFacade.getSubmodelRawPayload(any(), any(), any())).thenThrow( - new ItemNotFoundInCatalogException("test", "itemId")).thenReturn(""" + when(submodelFacade.getSubmodelPayload(any(), any(), any())).thenThrow( + new ItemNotFoundInCatalogException("test", "itemId")).thenReturn(new SubmodelDescriptor("cid", """ {"test": "test"} - """); + """)); when(jsonValidatorService.validate(any(), any())).thenReturn(ValidationResult.builder().valid(true).build()); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn( List.of("connector.endpoint.n1", "connector.endpoint.n2")); @@ -190,6 +188,7 @@ void shouldRequestForAllEndpoints() throws EdcClientException, InvalidSchemaExce assertThat(result.getSubmodels()).hasSize(1); assertThat(result.getSubmodels().get(0).getAspectType()).isEqualTo( "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart"); + assertThat(result.getSubmodels().get(0).getContractAgreementId()).isNull(); assertThat(result.getTombstones()).isEmpty(); } @@ -197,13 +196,13 @@ void shouldRequestForAllEndpoints() throws EdcClientException, InvalidSchemaExce void shouldCatchRestClientExceptionAndPutTombstone() throws SchemaNotFoundException { // given final ItemContainer.ItemContainerBuilder itemContainerShellWithTwoSubmodels = ItemContainer.builder() - .shell(shellDescriptor( + .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart", "testSerialPartEndpoint"), submodelDescriptorWithDspEndpoint( "urn:bamm:com.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", - "testSingleLevelBomAsBuiltEndpoint")))); + "testSingleLevelBomAsBuiltEndpoint"))))); // when when(semanticsHubFacade.getModelJsonSchema(any())).thenThrow( @@ -219,4 +218,8 @@ void shouldCatchRestClientExceptionAndPutTombstone() throws SchemaNotFoundExcept ProcessStep.SCHEMA_REQUEST); } + private static PartChainIdentificationKey createKey() { + return PartChainIdentificationKey.builder().globalAssetId("itemId").bpn("bpn123").build(); + } + } diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWireMockSupport.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWireMockSupport.java new file mode 100644 index 0000000000..d7af399fcc --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWireMockSupport.java @@ -0,0 +1,110 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.bpdm; + +import static com.github.tomakehurst.wiremock.client.WireMock.exactly; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; + +/** + * WireMock configurations and requests used for testing BPDM flow. + */ +public final class BpdmWireMockSupport { + + public static final String BPN_PATH = "/legal-entities/"; + public static final String BPDM_URL_TEMPLATE = "http://bpdm.test" + BPN_PATH + "{partnerId}?idType={idType}"; + + private BpdmWireMockSupport() { + } + + public static void bpdmWillReturnCompanyName(final String bpn, final String companyName) { + stubFor(get(urlPathEqualTo(BPN_PATH + bpn)).willReturn( + responseWithStatus(200).withBody(bpdmResponse(bpn, companyName)))); + } + + public static void bpdmWillNotFindCompanyName(final String bpn) { + stubFor(get(urlPathEqualTo(BPN_PATH + bpn)).willReturn(responseWithStatus(404))); + } + + public static void verifyBpdmWasCalledWithBPN(final String bpn, final int times) { + verify(exactly(times), getRequestedFor(urlPathEqualTo(BPN_PATH + bpn))); + } + + public static String bpdmResponse(final String bpn, final String companyName) { + return """ + { + "bpn": "%s", + "identifiers": [ + { + "value": "%s", + "type": { + "technicalKey": "BPN", + "name": "Business Partner Number", + "url": "" + }, + "issuingBody": { + "technicalKey": "CATENAX", + "name": "Catena-X", + "url": "" + }, + "status": { + "technicalKey": "UNKNOWN", + "name": "Unknown" + } + } + ], + "names": [ + { + "value": "%s", + "shortName": null, + "type": { + "technicalKey": "OTHER", + "name": "Any other alternative name used for a company, such as a specific language variant.", + "url": "" + }, + "language": { + "technicalKey": "undefined", + "name": "Undefined" + } + } + ], + "legalForm": null, + "status": null, + "profileClassifications": [], + "types": [ + { + "technicalKey": "UNKNOWN", + "name": "Unknown", + "url": "" + } + ], + "bankAccounts": [], + "roles": [], + "relations": [], + "currentness": "2022-07-26T08:17:38.737578Z" + } + """.formatted(bpn, bpn, companyName); + } + +} \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWiremockTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWiremockTest.java new file mode 100644 index 0000000000..b0c63f0713 --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWiremockTest.java @@ -0,0 +1,78 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.bpdm; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.eclipse.tractusx.irs.bpdm.BpdmWireMockSupport.BPDM_URL_TEMPLATE; +import static org.eclipse.tractusx.irs.bpdm.BpdmWireMockSupport.bpdmWillNotFindCompanyName; +import static org.eclipse.tractusx.irs.bpdm.BpdmWireMockSupport.bpdmWillReturnCompanyName; +import static org.eclipse.tractusx.irs.bpdm.BpdmWireMockSupport.verifyBpdmWasCalledWithBPN; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy; + +import java.util.Optional; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +@WireMockTest +class BpdmWiremockTest { + private static final String PROXY_SERVER_HOST = "127.0.0.1"; + private BpdmFacade bpdmFacade; + + @BeforeEach + void setUp(WireMockRuntimeInfo wireMockRuntimeInfo) { + final RestTemplate restTemplate = restTemplateProxy(PROXY_SERVER_HOST, wireMockRuntimeInfo.getHttpPort()); + + bpdmFacade = new BpdmFacade(new BpdmClientImpl(restTemplate, BPDM_URL_TEMPLATE)); + } + + @Test + void shouldResolveManufacturerName() { + // Arrange + final String bpn = "BPNL00000000TEST"; + bpdmWillReturnCompanyName(bpn, "TEST_BPN_DFT_1"); + + // Act + final Optional manufacturerName = bpdmFacade.findManufacturerName(bpn); + + // Assert + assertThat(manufacturerName).isPresent().contains("TEST_BPN_DFT_1"); + verifyBpdmWasCalledWithBPN(bpn, 1); + } + + @Test + void shouldReturnEmptyOnNotFound() { + // Arrange + final String bpn = "BPNL00000000TEST"; + bpdmWillNotFindCompanyName(bpn); + + // Act & Assert + // TODO (#405) fix implementation to not throw HttpClientErrorException$NotFound + assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy( + () -> bpdmFacade.findManufacturerName(bpn)); + verifyBpdmWasCalledWithBPN(bpn, 1); + } + +} diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/bpn/validation/BPNIncidentValidationTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/bpn/validation/BPNIncidentValidationTest.java index 99aac4453d..3b59380ce6 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/bpn/validation/BPNIncidentValidationTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/bpn/validation/BPNIncidentValidationTest.java @@ -24,6 +24,7 @@ package org.eclipse.tractusx.irs.ess.bpn.validation; import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.irs.util.TestMother.shell; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -33,6 +34,7 @@ import org.eclipse.tractusx.irs.component.GlobalAssetIdentification; import org.eclipse.tractusx.irs.component.Job; import org.eclipse.tractusx.irs.component.Jobs; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; import org.eclipse.tractusx.irs.component.assetadministrationshell.IdentifierKeyValuePair; import org.eclipse.tractusx.irs.component.partasplanned.PartAsPlanned; @@ -44,24 +46,6 @@ class BPNIncidentValidationTest { - private static Jobs jobResult(final String parentId, final Map cxIdBPNMap) { - final List shells = new ArrayList<>(); - cxIdBPNMap.forEach((s, s2) -> shells.add(createShell(s, s2))); - final GlobalAssetIdentification globalAssetId = GlobalAssetIdentification.of(parentId); - final Job job = Job.builder().globalAssetId(globalAssetId).build(); - return Jobs.builder().job(job).shells(shells).build(); - } - - private static AssetAdministrationShellDescriptor createShell(final String catenaXId, final String bpn) { - return AssetAdministrationShellDescriptor.builder() - .globalAssetId(catenaXId) - .specificAssetIds(List.of(IdentifierKeyValuePair.builder() - .name("manufacturerId") - .value(bpn) - .build())) - .build(); - } - @Test void shouldReturnNoWhenBPNsDoNotContainShellBPNs() { // Arrange @@ -77,7 +61,7 @@ void shouldReturnNoWhenBPNsDoNotContainShellBPNs() { final Jobs jobs = jobResult(parentId, cxIdBPNMap); // Act - final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells(), bpns); + final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells().stream().map(Shell::payload).toList(), bpns); // Assert assertThat(actual).isEqualTo(SupplyChainImpacted.NO); @@ -98,7 +82,7 @@ void shouldReturnYesWhenBPNsContainShellBPNs() { final Jobs jobs = jobResult(parentId, cxIdBPNMap); // Act - final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells(), bpns); + final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells().stream().map(Shell::payload).toList(), bpns); // Assert assertThat(actual).isEqualTo(SupplyChainImpacted.YES); @@ -114,7 +98,7 @@ void shouldReturnYesWhenNoChildrenAndParentContainsBPN() { final Jobs jobs = jobResult(parentId, cxIdBPNMap); // Act - final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells(), bpns); + final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells().stream().map(Shell::payload).toList(), bpns); // Assert assertThat(actual).isEqualTo(SupplyChainImpacted.YES); @@ -130,7 +114,7 @@ void shouldReturnNoWhenNoChildrenAndParentDoesNotContainBPN() { final Jobs jobs = jobResult(parentId, cxIdBPNMap); // Act - final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells(), bpns); + final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells().stream().map(Shell::payload).toList(), bpns); // Assert assertThat(actual).isEqualTo(SupplyChainImpacted.NO); @@ -145,7 +129,7 @@ void shouldReturnUnknownWhenJobContainsNoShells() { final Jobs jobs = jobResult(parentId, cxIdBPNMap); // Act - final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells(), bpns); + final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells().stream().map(Shell::payload).toList(), bpns); // Assert assertThat(actual).isEqualTo(SupplyChainImpacted.UNKNOWN); @@ -163,11 +147,11 @@ void shouldReturnUnknownWhenJobContainsShellWithoutBPN() { .build(); final Jobs jobs = Jobs.builder() .job(Job.builder().globalAssetId(GlobalAssetIdentification.of(parentId)).build()) - .shells(List.of(shellDescriptor)) + .shells(List.of(shell("", shellDescriptor))) .build(); // Act - final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells(), bpns); + final SupplyChainImpacted actual = BPNIncidentValidation.jobContainsIncidentBPNs(jobs.getShells().stream().map(Shell::payload).toList(), bpns); // Assert assertThat(actual).isEqualTo(SupplyChainImpacted.UNKNOWN); @@ -284,4 +268,22 @@ void shouldReturnYesWhenNowBeforeValidityPeriod() { // Assert assertThat(supplyChainImpacted).isEqualTo(SupplyChainImpacted.YES); } + + private static Jobs jobResult(final String parentId, final Map cxIdBPNMap) { + final List shells = new ArrayList<>(); + cxIdBPNMap.forEach((s, s2) -> shells.add(shell("", createShell(s, s2)))); + final GlobalAssetIdentification globalAssetId = GlobalAssetIdentification.of(parentId); + final Job job = Job.builder().globalAssetId(globalAssetId).build(); + return Jobs.builder().job(job).shells(shells).build(); + } + + private static AssetAdministrationShellDescriptor createShell(final String catenaXId, final String bpn) { + return AssetAdministrationShellDescriptor.builder() + .globalAssetId(catenaXId) + .specificAssetIds(List.of(IdentifierKeyValuePair.builder() + .name("manufacturerId") + .value(bpn) + .build())) + .build(); + } } \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListenerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListenerTest.java index f0fbdaa709..c811a1817f 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListenerTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListenerTest.java @@ -25,6 +25,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.irs.ess.service.EdcRegistration.ASSET_ID_REQUEST_RECURSIVE; +import static org.eclipse.tractusx.irs.util.TestMother.shell; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -46,6 +47,7 @@ import org.eclipse.tractusx.irs.component.Jobs; import org.eclipse.tractusx.irs.component.LinkedItem; import org.eclipse.tractusx.irs.component.Relationship; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.Submodel; import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; import org.eclipse.tractusx.irs.component.assetadministrationshell.IdentifierKeyValuePair; @@ -88,27 +90,6 @@ class InvestigationJobProcessingEventListenerTest { @Captor ArgumentCaptor> edcNotificationCaptor; - private static AssetAdministrationShellDescriptor createShell(final String catenaXId, final String bpn) { - return AssetAdministrationShellDescriptor.builder() - .globalAssetId(catenaXId) - .specificAssetIds(List.of(IdentifierKeyValuePair.builder() - .name("manufacturerId") - .value(bpn) - .build())) - .build(); - } - - private static Relationship createRelationship(final String lifecycle, final String bpn, final String parentId, - final String childId) { - return Relationship.builder() - .aspectType(lifecycle) - .bpn(bpn) - .catenaXId(GlobalAssetIdentification.of(parentId)) - .linkedItem( - LinkedItem.builder().childCatenaXId(GlobalAssetIdentification.of(childId)).build()) - .build(); - } - @BeforeEach void mockInit() { createMockForJobIdAndShell(jobId, "bpn", List.of(createRelationship("SingleLevelBomAsPlanned", "BPN123", @@ -345,7 +326,7 @@ private void createMockForJobIdAndShell(final UUID mockedJobId, final String moc private void createMockForJobIdAndShell(final UUID mockedJobId, final String mockedShell, final List relationships, final List incindentBPNSs, final List submodels) { createMockJob(mockedJobId, relationships, incindentBPNSs, submodels, - List.of(createShell(UUID.randomUUID().toString(), mockedShell))); + List.of(shell("", createShell(UUID.randomUUID().toString(), mockedShell)))); } private void createMockForJobIdAndShell(final UUID mockedJobId, final String mockedShell, @@ -364,12 +345,12 @@ private void createMockForJobIdAndShellsWithMissingSiteIt(final UUID mockedJobId private void createMockForJobIdAndShells(final UUID mockedJobId, final List bpns, final List submodels) { createMockJob(mockedJobId, List.of(), List.of("BPNS000000000DDD"), submodels, - bpns.stream().map(bpn -> createShell(UUID.randomUUID().toString(), bpn)).toList()); + bpns.stream().map(bpn -> shell("", createShell(UUID.randomUUID().toString(), bpn))).toList()); } private void createMockJob(final UUID mockedJobId, final List relationships, final List incindentBPNSs, final List submodels, - final List shells) { + final List shells) { final Jobs jobs = Jobs.builder() .job(Job.builder() .id(mockedJobId) @@ -403,7 +384,7 @@ private static Submodel getPartSiteInformationAsPlanned() { } """; return Submodel.from("test2", - "urn:bamm:io.catenax.part_site_information_as_planned:1.0.0#PartSiteInformationAsPlanned", + "urn:bamm:io.catenax.part_site_information_as_planned:1.0.0#PartSiteInformationAsPlanned", "cid", StringMapper.mapFromString(partSiteInformationAsPlannedRaw, Map.class)); } @@ -421,7 +402,7 @@ private static Submodel getPartSiteInformationAsPlannedWithoutSiteId() { } """; return Submodel.from("test2", - "urn:bamm:io.catenax.part_site_information_as_planned:1.0.0#PartSiteInformationAsPlanned", + "urn:bamm:io.catenax.part_site_information_as_planned:1.0.0#PartSiteInformationAsPlanned", "cid", StringMapper.mapFromString(partSiteInformationAsPlannedRaw, Map.class)); } @@ -440,8 +421,29 @@ private static Submodel getPartAsPlanned() { } } """; - return Submodel.from("test1", "urn:bamm:io.catenax.part_as_planned:1.0.1#PartAsPlanned", + return Submodel.from("test1", "urn:bamm:io.catenax.part_as_planned:1.0.1#PartAsPlanned", "cid", StringMapper.mapFromString(partAsPlannedRaw, Map.class)); } + private static AssetAdministrationShellDescriptor createShell(final String catenaXId, final String bpn) { + return AssetAdministrationShellDescriptor.builder() + .globalAssetId(catenaXId) + .specificAssetIds(List.of(IdentifierKeyValuePair.builder() + .name("manufacturerId") + .value(bpn) + .build())) + .build(); + } + + private static Relationship createRelationship(final String lifecycle, final String bpn, final String parentId, + final String childId) { + return Relationship.builder() + .aspectType(lifecycle) + .bpn(bpn) + .catenaXId(GlobalAssetIdentification.of(parentId)) + .linkedItem( + LinkedItem.builder().childCatenaXId(GlobalAssetIdentification.of(childId)).build()) + .build(); + } + } diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWireMockSupport.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWireMockSupport.java new file mode 100644 index 0000000000..640755a10e --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWireMockSupport.java @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.semanticshub; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.exactly; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; + +/** + * WireMock configurations and requests used for testing Semantic Hub fLow. + */ +public final class SemanticHubWireMockSupport { + public static final String BATCH_URN = "urn:samm:io.catenax.batch:2.0.0%23Batch"; + private static final String SINGLE_LEVEL_BOM_AS_BUILT_URN = "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0%23SingleLevelBomAsBuilt"; + public static final String SCHEMA_PATH_PLACEHOLDER = "/models/%s/json-schema"; + public static final String SEMANTIC_HUB_SCHEMA_URL = + "http://semantic.hub" + SCHEMA_PATH_PLACEHOLDER.formatted("{urn}"); + public static final String MODELS_PATH = "/models"; + + private SemanticHubWireMockSupport() { + } + + public static void semanticHubWillReturnBatchSchema() { + schemaResponse200(SCHEMA_PATH_PLACEHOLDER.formatted(BATCH_URN), "semantichub/batch-2.0.0-schema.json"); + } + + public static void semanticHubWillReturnSingleLevelBomAsBuiltSchema() { + schemaResponse200(SCHEMA_PATH_PLACEHOLDER.formatted(SINGLE_LEVEL_BOM_AS_BUILT_URN), + "semantichub/singleLevelBomAsBuilt-2.0.0-schema.json"); + } + + private static void schemaResponse200(final String urlRegex, final String fileName) { + stubFor(get(urlPathMatching(urlRegex)).withHost(equalTo("semantic.hub")) + .willReturn(responseWithStatus(200).withBodyFile(fileName))); + } + + static void semanticHubWillReturnPagedModels(final int page, final int pageSize, final String fileName) { + stubFor(get(urlPathEqualTo(MODELS_PATH)).withHost(equalTo("semantic.hub")) + .withQueryParam("page", equalTo(String.valueOf(page))) + .withQueryParam("pageSize", equalTo(String.valueOf(pageSize))) + .willReturn(responseWithStatus(200).withBodyFile(fileName))); + } + + public static void semanticHubWillReturnAllModels(final String fileName) { + stubFor(get(urlPathEqualTo(MODELS_PATH)).withHost(equalTo("semantic.hub")) + .willReturn(responseWithStatus(200).withBodyFile(fileName))); + } + + static void semanticHubWillThrowErrorForSemanticModel(final String semanticModel) { + final String url = SCHEMA_PATH_PLACEHOLDER.formatted(semanticModel); + stubFor(get(urlPathEqualTo(url)).willReturn(responseWithStatus(500))); + } + + static void verifySemanticHubWasCalledForModel(final String model, final int times) { + verify(exactly(times), getRequestedFor(urlPathMatching(SCHEMA_PATH_PLACEHOLDER.formatted(model)))); + } + + static void verifySemanticHubWasCalledForAllModels(final int times) { + verify(exactly(times), getRequestedFor(urlPathEqualTo(MODELS_PATH))); + } +} \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWiremockTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWiremockTest.java index d4f13500ee..6558c3d523 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWiremockTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWiremockTest.java @@ -23,81 +23,85 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.semanticshub; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.configureFor; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.givenThat; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.eclipse.tractusx.irs.semanticshub.SemanticHubWireMockSupport.SEMANTIC_HUB_SCHEMA_URL; +import static org.eclipse.tractusx.irs.semanticshub.SemanticHubWireMockSupport.semanticHubWillReturnBatchSchema; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy; -import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; import org.eclipse.tractusx.irs.configuration.SemanticsHubConfiguration; import org.eclipse.tractusx.irs.services.validation.SchemaNotFoundException; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.web.client.RestTemplate; +@WireMockTest class SemanticHubWiremockTest { - private WireMockServer wireMockServer; - + private static final String PROXY_SERVER_HOST = "127.0.0.1"; private SemanticsHubFacade semanticsHubFacade; - private SemanticsHubConfiguration config; @BeforeEach - void configureSystemUnderTest() { - this.wireMockServer = new WireMockServer(options().dynamicPort()); - this.wireMockServer.start(); - configureFor(this.wireMockServer.port()); + void configureSystemUnderTest(WireMockRuntimeInfo wireMockRuntimeInfo) { + final RestTemplate restTemplate = restTemplateProxy(PROXY_SERVER_HOST, wireMockRuntimeInfo.getHttpPort()); - config = new SemanticsHubConfiguration(); + final SemanticsHubConfiguration config = new SemanticsHubConfiguration(); config.setPageSize(10); - config.setUrl(String.format("http://localhost:%d/models", this.wireMockServer.port())); - config.setModelJsonSchemaEndpoint("sem.hub/models/{urn}/json-schema"); + config.setUrl("http://semantic.hub/models"); + config.setModelJsonSchemaEndpoint(SEMANTIC_HUB_SCHEMA_URL); - final RestTemplate restTemplate = new RestTemplate(); final SemanticsHubClient semanticsHubClient = new SemanticsHubClientImpl(restTemplate, config); semanticsHubFacade = new SemanticsHubFacade(semanticsHubClient); } - @AfterEach - void tearDown() { - this.wireMockServer.stop(); - } - @Test void shouldReturn1Page() throws SchemaNotFoundException { - givenThat(get(urlPathEqualTo("/models")).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBodyFile("all-models-page.json"))); + SemanticHubWireMockSupport.semanticHubWillReturnAllModels("all-models-page.json"); final AspectModels allAspectModels = semanticsHubFacade.getAllAspectModels(); assertThat(allAspectModels.models()).isNotEmpty(); assertThat(allAspectModels.models().get(0).name()).isEqualTo("SerialPartTypization"); + SemanticHubWireMockSupport.verifySemanticHubWasCalledForAllModels(1); } @Test void shouldReturn2Pages() throws SchemaNotFoundException { - givenThat(get(urlPathEqualTo("/models")).withQueryParam("page", equalTo("0")) - .withQueryParam("pageSize", equalTo("10")) - .willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBodyFile("all-models-page1.json"))); - givenThat(get(urlPathEqualTo("/models")).withQueryParam("page", equalTo("1")) - .withQueryParam("pageSize", equalTo("10")) - .willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBodyFile("all-models-page2.json"))); + SemanticHubWireMockSupport.semanticHubWillReturnPagedModels(0, 10, "all-models-page1.json"); + SemanticHubWireMockSupport.semanticHubWillReturnPagedModels(1, 10, "all-models-page2.json"); final AspectModels allAspectModels = semanticsHubFacade.getAllAspectModels(); assertThat(allAspectModels.models()).hasSize(20); assertThat(allAspectModels.models().get(0).name()).isEqualTo("SerialPartTypization"); + SemanticHubWireMockSupport.verifySemanticHubWasCalledForAllModels(2); } + + @Test + void shouldReturnJsonSchema() throws SchemaNotFoundException { + // Arrange + semanticHubWillReturnBatchSchema(); + + // Act + final String modelJsonSchema = semanticsHubFacade.getModelJsonSchema("urn:samm:io.catenax.batch:2.0.0#Batch"); + + // Assert + assertThat(modelJsonSchema).contains("urn_samm_io.catenax.batch_2.0.0_CatenaXIdTrait") + .contains("A batch is a quantity of (semi-) finished products or (raw) material"); + SemanticHubWireMockSupport.verifySemanticHubWasCalledForModel("urn:samm:io.catenax.batch:2.0.0%23Batch", 1); + } + + @Test + void shouldThrowSchemaExceptionWhenSchemaNotFound() { + // Arrange + final String semanticModel = "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0%23SingleLevelBomAsBuilt"; + SemanticHubWireMockSupport.semanticHubWillThrowErrorForSemanticModel(semanticModel); + + // Act & Assert + assertThatExceptionOfType(SchemaNotFoundException.class).isThrownBy(() -> semanticsHubFacade.getModelJsonSchema( + "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt")); + SemanticHubWireMockSupport.verifySemanticHubWasCalledForModel(semanticModel, 1); + } + } diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/util/JobResponseAnalyzerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/util/JobResponseAnalyzerTest.java deleted file mode 100644 index c2c7e4e692..0000000000 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/util/JobResponseAnalyzerTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022,2024 - * 2022: ZF Friedrichshafen AG - * 2022: ISTOS GmbH - * 2022,2024: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * 2022,2023: BOSCH AG - * Copyright (c) 2021,2024 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.irs.util; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.eclipse.tractusx.irs.component.Jobs; -import org.eclipse.tractusx.irs.component.ProcessingError; -import org.eclipse.tractusx.irs.component.Tombstone; -import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; -import org.junit.jupiter.api.Test; - -class JobResponseAnalyzerTest { - - @Test - void parseAndPrintJobResponseResults() throws IOException { - final String rootGlobalAssetId = "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af"; - final int rootDepth = 0; - final Jobs jobs = this.loadJobsResponseFromFile("src/test/resources/__files/jobResponse.json"); - - this.findAndPrintChildOf(jobs, rootGlobalAssetId, rootDepth); - - assertThat(jobs).isNotNull(); - } - - private List findAndPrintChildOf(final Jobs jobs, final String globalAssetId, final int depth) { - final List childGlobalIds = jobs.getRelationships() - .stream() - .filter(rel -> rel.getCatenaXId() - .getGlobalAssetId() - .equals(globalAssetId)) - .map(rel -> rel.getLinkedItem().getChildCatenaXId().getGlobalAssetId()) - .collect(Collectors.toList()); - - System.out.println(); - System.out.println("Depth: " + depth); - System.out.println("GlobalAssetId: " + globalAssetId); - System.out.println("BPN: " + this.findBpnOf(jobs.getShells(), globalAssetId)); - System.out.println("Child GlobalAssetId's: " + childGlobalIds); - System.out.println("Tombstones: "); - this.findTombstoneOf(jobs.getTombstones(), globalAssetId).forEach(System.out::println); - - childGlobalIds.forEach(child -> this.findAndPrintChildOf(jobs, child, depth + 1)); - - return childGlobalIds; - } - - private String findBpnOf(final List shells, final String globalAssetId) { - return shells.stream() - .filter(shell -> shell.getGlobalAssetId().equals(globalAssetId)) - .findFirst() - .flatMap(AssetAdministrationShellDescriptor::findManufacturerId) - .orElse(""); - } - - private List findTombstoneOf(final List tombstones, final String globalAssetId) { - return tombstones.stream() - .filter(tombstone -> tombstone.getCatenaXId().equals(globalAssetId)) - .map(Tombstone::getProcessingError) - .map(ProcessingError::getErrorDetail) - .collect(Collectors.toList()); - } - - private Jobs loadJobsResponseFromFile(final String filePath) throws IOException { - final File file = new File(filePath); - - final ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); - objectMapper.registerModule(new Jdk8Module()); - objectMapper.registerModule(new JavaTimeModule()); - - return objectMapper.readValue(file, Jobs.class); - } - -} \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/util/TestMother.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/util/TestMother.java index 87fb365b4e..9d9ac34dee 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/util/TestMother.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/util/TestMother.java @@ -44,6 +44,7 @@ import org.eclipse.tractusx.irs.component.RegisterBpnInvestigationBatchOrder; import org.eclipse.tractusx.irs.component.RegisterJob; import org.eclipse.tractusx.irs.component.Relationship; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; import org.eclipse.tractusx.irs.component.assetadministrationshell.Endpoint; import org.eclipse.tractusx.irs.component.assetadministrationshell.IdentifierKeyValuePair; @@ -154,6 +155,7 @@ public static JobParameter jobParameter() { .direction(Direction.DOWNWARD) .aspects(List.of(AspectType.SERIAL_PART.toString(), AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString())) + .auditContractNegotiation(false) .build(); } @@ -196,6 +198,17 @@ public static JobParameter jobParameterCollectBpns() { .build(); } + public static JobParameter jobParameterAuditContractNegotiation() { + return JobParameter.builder() + .depth(5) + .bomLifecycle(BomLifecycle.AS_BUILT) + .direction(Direction.DOWNWARD) + .aspects(List.of(AspectType.SERIAL_PART.toString(), + AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString())) + .auditContractNegotiation(true) + .build(); + } + public static MeterRegistryService simpleMeterRegistryService() { return new MeterRegistryService(new SimpleMeterRegistry()); } @@ -251,6 +264,10 @@ public static SubmodelDescriptor submodelDescriptorWithoutHref(final String sema return submodelDescriptorWithDspEndpoint(semanticId, null); } + public static Shell shell(String contractAgreementId, AssetAdministrationShellDescriptor shell) { + return new Shell(contractAgreementId, shell); + } + public static AssetAdministrationShellDescriptor shellDescriptor( final List submodelDescriptors) { return AssetAdministrationShellDescriptor.builder() diff --git a/irs-api/src/test/resources/__files/edc/responseCatalog.json b/irs-api/src/test/resources/__files/edc/responseCatalog.json deleted file mode 100644 index e6ba0d04c1..0000000000 --- a/irs-api/src/test/resources/__files/edc/responseCatalog.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "id": "default", - "contractOffers": [ - { - "id": "3:afb3dca6-e7cc-42b6-98bb-c6f948121c7a", - "policy": { - "permissions": [ - { - "edctype": "dataspaceconnector:permission", - "uid": null, - "target": "urn:uuid:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-urn:uuid:31b614f5-ec14-4ed2-a509-e7b7780083e7", - "action": { - "type": "USE", - "includedIn": null, - "constraint": null - }, - "assignee": null, - "assigner": null, - "constraints": [], - "duties": [] - } - ], - "prohibitions": [], - "obligations": [], - "extensibleProperties": {}, - "inheritsFrom": null, - "assigner": null, - "assignee": null, - "target": "urn:uuid:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-urn:uuid:31b614f5-ec14-4ed2-a509-e7b7780083e7", - "@type": { - "@policytype": "set" - } - }, - "asset": { - "id": "urn:uuid:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-urn:uuid:31b614f5-ec14-4ed2-a509-e7b7780083e7", - "createdAt": 1669196292759, - "properties": { - "asset:prop:byteSize": null, - "asset:prop:description": "product description", - "asset:prop:contenttype": "application/json", - "asset:prop:policy-id": "use-eu", - "asset:prop:id": "urn:uuid:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-urn:uuid:31b614f5-ec14-4ed2-a509-e7b7780083e7", - "asset:prop:fileName": null - } - }, - "assetId": null, - "provider": "urn:connector:provider", - "consumer": "urn:connector:consumer", - "offerStart": null, - "offerEnd": null, - "contractStart": null, - "contractEnd": null - }, - { - "id": "notification-receipt-cd:b3693bd7-192e-4b2f-b24b-a1d3e3518dee", - "policy": { - "permissions": [ - { - "edctype": "dataspaceconnector:permission", - "uid": null, - "target": "notification-receipt", - "action": { - "type": "USE", - "includedIn": null, - "constraint": null - }, - "assignee": null, - "assigner": null, - "constraints": [ - { - "edctype": "AtomicConstraint", - "leftExpression": { - "edctype": "dataspaceconnector:literalexpression", - "value": "idsc:PURPOSE" - }, - "rightExpression": { - "edctype": "dataspaceconnector:literalexpression", - "value": "Investigation" - }, - "operator": "EQ" - } - ], - "duties": [] - } - ], - "prohibitions": [], - "obligations": [], - "extensibleProperties": {}, - "inheritsFrom": null, - "assigner": null, - "assignee": null, - "target": "notification-receipt", - "@type": { - "@policytype": "set" - } - }, - "asset": { - "id": "notification-receipt", - "createdAt": 1680088494977, - "properties": { - "asset:prop:byteSize": null, - "asset:prop:name": "Asset to receive investigations", - "asset:prop:contenttype": "application/json", - "asset:prop:notificationtype": "investigation", - "asset:prop:notificationmethod": "receive", - "asset:prop:id": "notification-receipt", - "asset:prop:fileName": null - } - }, - "assetId": null, - "provider": "urn:connector:provider", - "consumer": "urn:connector:consumer", - "offerStart": null, - "offerEnd": null, - "contractStart": null, - "contractEnd": null - } - ] -} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/edc/responseGetNegotiationConfirmed.json b/irs-api/src/test/resources/__files/edc/responseGetNegotiationConfirmed.json deleted file mode 100644 index 4d0c6ae3cc..0000000000 --- a/irs-api/src/test/resources/__files/edc/responseGetNegotiationConfirmed.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "createdAt": 1669197004371, - "updatedAt": 1669197045765, - "contractAgreementId": "1bbaec6e-c316-4e1e-8258-c07a648cc43c", - "counterPartyAddress": "http://edc.io/BPNL0000000BB2OK/api/v1/ids/data", - "errorDetail": "", - "id": "1cbaec6e-c316-4e3e-8258-c07a648cc44a", - "protocol": "ids-multipart", - "state": "CONFIRMED", - "type": "CONSUMER" -} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/edc/responseStartNegotiation.json b/irs-api/src/test/resources/__files/edc/responseStartNegotiation.json deleted file mode 100644 index 083246511f..0000000000 --- a/irs-api/src/test/resources/__files/edc/responseStartNegotiation.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "id": "1cbaec6e-c316-4e3e-8258-c07a648cc44a" -} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/edc/responseStartTransferprocess.json b/irs-api/src/test/resources/__files/edc/responseStartTransferprocess.json deleted file mode 100644 index 88f6783778..0000000000 --- a/irs-api/src/test/resources/__files/edc/responseStartTransferprocess.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "id": "1b21e963-0bc5-422a-b30d-fd3511861d88" -} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/batch-1.json b/irs-api/src/test/resources/__files/integrationtesting/batch-1.json new file mode 100644 index 0000000000..b044897ad0 --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/batch-1.json @@ -0,0 +1,22 @@ +{ + "localIdentifiers": [ + { + "value": "BPNL00000000TEST", + "key": "manufacturerId" + }, + { + "value": "BID12345678", + "key": "batchId" + } + ], + "manufacturingInformation": { + "date": "2022-02-04T14:48:54", + "country": "HUR" + }, + "catenaXId": "urn:uuid:334cce52-1f52-4bc9-9dd1-410bbe497bbc", + "partTypeInformation": { + "manufacturerPartId": "123-0.740-3434-A", + "classification": "product", + "nameAtManufacturer": "Cathode" + } +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/batch-2.json b/irs-api/src/test/resources/__files/integrationtesting/batch-2.json new file mode 100644 index 0000000000..d86f962cb3 --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/batch-2.json @@ -0,0 +1,22 @@ +{ + "localIdentifiers": [ + { + "value": "BPNL00000000TEST", + "key": "manufacturerId" + }, + { + "value": "BID12345678", + "key": "batchId" + } + ], + "manufacturingInformation": { + "date": "2022-02-04T14:48:54", + "country": "HUR" + }, + "catenaXId": "urn:uuid:7e4541ea-bb0f-464c-8cb3-021abccbfaf5", + "partTypeInformation": { + "manufacturerPartId": "123-0.740-3434-A", + "classification": "product", + "nameAtManufacturer": "Polyamid" + } +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/batch-3.json b/irs-api/src/test/resources/__files/integrationtesting/batch-3.json new file mode 100644 index 0000000000..f62643532a --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/batch-3.json @@ -0,0 +1,22 @@ +{ + "localIdentifiers": [ + { + "value": "BPNL00000000TEST", + "key": "manufacturerId" + }, + { + "value": "BID12345678", + "key": "batchId" + } + ], + "manufacturingInformation": { + "date": "2022-02-04T14:48:54", + "country": "HUR" + }, + "catenaXId": "urn:uuid:a314ad6b-77ea-417e-ae2d-193b3e249e99", + "partTypeInformation": { + "manufacturerPartId": "123-0.740-3434-A", + "classification": "product", + "nameAtManufacturer": "Generic Chemical" + } +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-1.json b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-1.json new file mode 100644 index 0000000000..167a940ce7 --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-1.json @@ -0,0 +1,16 @@ +{ + "catenaXId": "urn:uuid:334cce52-1f52-4bc9-9dd1-410bbe497bbc", + "childItems": [ + { + "catenaXId": "urn:uuid:7e4541ea-bb0f-464c-8cb3-021abccbfaf5", + "quantity": { + "quantityNumber": 0.2014, + "measurementUnit": "unit:kilogram" + }, + "hasAlternatives": true, + "businessPartner": "BPNL00000000TEST", + "createdOn": "2022-02-03T14:48:54.709Z", + "lastModifiedOn": "2022-02-03T14:48:54.709Z" + } + ] +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-2.json b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-2.json new file mode 100644 index 0000000000..594e51a4f2 --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-2.json @@ -0,0 +1,16 @@ +{ + "catenaXId": "urn:uuid:7e4541ea-bb0f-464c-8cb3-021abccbfaf5", + "childItems": [ + { + "catenaXId": "urn:uuid:a314ad6b-77ea-417e-ae2d-193b3e249e99", + "quantity": { + "quantityNumber": 0.2014, + "measurementUnit": "unit:kilogram" + }, + "hasAlternatives": true, + "businessPartner": "BPNL00000000TEST", + "createdOn": "2022-02-03T14:48:54.709Z", + "lastModifiedOn": "2022-02-03T14:48:54.709Z" + } + ] +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-3.json b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-3.json new file mode 100644 index 0000000000..69e7778525 --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-3.json @@ -0,0 +1,4 @@ +{ + "catenaXId": "urn:uuid:a314ad6b-77ea-417e-ae2d-193b3e249e99", + "childItems": [] +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/jobResponse.json b/irs-api/src/test/resources/__files/jobResponse.json deleted file mode 100644 index 63b69bbe62..0000000000 --- a/irs-api/src/test/resources/__files/jobResponse.json +++ /dev/null @@ -1,2682 +0,0 @@ -{ - "job": { - "jobId": "be315f9c-769e-4cc6-a07b-78b94c521923", - "globalAssetId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "jobState": "COMPLETED", - "exception": null, - "createdOn": "2022-11-08T08:35:49.123735015Z", - "startedOn": "2022-11-08T08:35:49.123946811Z", - "lastModifiedOn": "2022-11-08T08:40:41.659932496Z", - "jobCompleted": "2022-11-08T08:40:41.659935596Z", - "owner": "sa-cl6-cx-9", - "summary": { - "asyncFetchedItems": { - "running": 0, - "completed": 38, - "failed": 6 - } - }, - "jobParameter": { - "bomLifecycle": "asBuilt", - "aspects": [ - "SerialPartTypization" - ], - "depth": 100, - "direction": "downward", - "collectAspects": false, - "callbackUrl": null - } - }, - "relationships": [ - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:8ae3c8a9-2c80-4223-a798-db94c854465b" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:9abcceb1-02ed-4384-83bb-e3f5b3be77d4" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:ef4ef0e2-4658-464c-ad1e-eae5afb77942" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:50edf0fc-95a7-40b3-ad43-9156f3b81768" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:57ced45e-4f98-4aa4-bb67-6f7c51b90a4b" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:18f9d1ef-100e-4c0a-bb83-a407c644fcba" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:73477f4a-f49f-4306-b30f-3ab36ef72e6f" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:3940c819-dd95-422b-aa32-f7d3f1930de8" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:f2e6d0cd-5a9a-451d-a583-510c5209f64c" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:ec215159-fae3-47d8-98c6-d67b7ffe7c0c" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:6c2f4f41-3924-43da-969d-1afee232194c" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:b32a9cda-b026-4715-b253-c08a7d11b840" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:0391b219-9bf5-44cc-89af-e5db399a7d21" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:7b2484b1-21b5-434a-aa2b-fd5434cc7ae3" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:868df1e0-b497-4059-a0d3-3899630020da" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:67885579-9ddc-4daa-b59e-d7348e9581d3" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:8fc21b03-1796-44af-bd52-c6998bfae1bb" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:d3ab8367-7d12-4991-bd89-cea117a62297" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:087372bf-e9f0-49e6-98e8-0ce98140605c" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:f3f33eec-d7ed-44b5-8cf2-752b6c2c22ca" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:ad1f1627-a57e-481f-9cff-10990674f486" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:643ff5cd-4468-4041-95d9-eeb2cc42c487" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:fbd019cc-6eda-4416-a668-e5d83271781d" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:00ce78e5-3bf3-4847-92c1-28771e83f998" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:09c48e81-5c73-4e1a-85a2-0bf2fff4dada" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:e35161e7-803d-4746-9528-db9d9814c692" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:5c48d1a1-bc81-4a9d-84db-53fc9c69e677" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:8c21d03f-b15e-427f-9cd0-c23701ed8f9c" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:98484975-27ed-4c79-ad94-c82c2c0360e8" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:e83c5a1f-7558-4978-990f-9c796224ce63" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:b995cae9-0196-4ab5-b768-798eda1b5726" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:867a1fd9-6a8b-4f56-a5ef-2b61f12f8bce" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:a9d00358-d560-4a4b-98cc-d3a4d61c253b" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:d7d61cd8-27b2-4707-a4c2-d735cf1a0dcd" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:9abcceb1-02ed-4384-83bb-e3f5b3be77d4", - "linkedItem": { - "quantity": { - "quantityNumber": 1.0, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:piece" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:68766b51-f794-4033-bcb1-f635ed309cd4" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:9abcceb1-02ed-4384-83bb-e3f5b3be77d4", - "linkedItem": { - "quantity": { - "quantityNumber": 0.2001, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:kilogram" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:68c9b1bf-b2c1-456a-883c-2aac5f5cb5f4" - }, - "aspectType": "AssemblyPartRelationship" - }, - { - "catenaXId": "urn:uuid:9abcceb1-02ed-4384-83bb-e3f5b3be77d4", - "linkedItem": { - "quantity": { - "quantityNumber": 0.3301, - "measurementUnit": { - "datatypeURI": "urn:bamm:io.openmanufacturing:meta-model:1.0.0#curie", - "lexicalValue": "unit:kilogram" - } - }, - "lifecycleContext": "asBuilt", - "assembledOn": "2022-02-03T14:48:54.709Z", - "lastModifiedOn": "2022-02-03T14:48:54.709Z", - "childCatenaXId": "urn:uuid:816f9c8d-6ef1-439e-89de-ef1d34481c17" - }, - "aspectType": "AssemblyPartRelationship" - } - ], - "shells": [ - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:8ae3c8a9-2c80-4223-a798-db94c854465b", - "idShort": "Taillight front_BPNL00000003AYRE_NO-577666297086581459258590", - "identification": "urn:uuid:858951f5-fb9c-4ec2-93be-e49fcc2c9361", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-577666297086581459258590", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "78744126-74", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:3b9fd040-5eb1-42f7-b35c-fc1fe48899ff", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:858951f5-fb9c-4ec2-93be-e49fcc2c9361-urn:uuid:3b9fd040-5eb1-42f7-b35c-fc1fe48899ff/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:73477f4a-f49f-4306-b30f-3ab36ef72e6f", - "idShort": "Exterior mirror left_BPNL00000003AYRE_NO-637839747432704841361574", - "identification": "urn:uuid:21527461-ab6d-4e52-b703-39c656f92d75", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-637839747432704841361574", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "65529521-37", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:594bf514-ec44-479d-90d6-d1544450a759", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:21527461-ab6d-4e52-b703-39c656f92d75-urn:uuid:594bf514-ec44-479d-90d6-d1544450a759/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:ef4ef0e2-4658-464c-ad1e-eae5afb77942", - "idShort": "Rims_BPNL00000003AYRE_NO-698813188915827969458218", - "identification": "urn:uuid:73ed3cf8-c124-444b-9524-95d1e17185e3", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-698813188915827969458218", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "08901347-87", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:a025a127-82e2-4361-84da-b94305e7191f", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:73ed3cf8-c124-444b-9524-95d1e17185e3-urn:uuid:a025a127-82e2-4361-84da-b94305e7191f/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:ec215159-fae3-47d8-98c6-d67b7ffe7c0c", - "idShort": "Bumper rear_BPNL00000003AYRE_NO-182909772702645317572786", - "identification": "urn:uuid:66e8a142-dcf6-47d7-8a9a-03c45187b0ee", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-182909772702645317572786", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "22768257-25", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:39960158-4d7c-4361-b043-e833c6dc1629", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:66e8a142-dcf6-47d7-8a9a-03c45187b0ee-urn:uuid:39960158-4d7c-4361-b043-e833c6dc1629/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:67885579-9ddc-4daa-b59e-d7348e9581d3", - "idShort": "Catalysator_BPNL00000003AYRE_NO-699623586834062230296181", - "identification": "urn:uuid:5d403d5d-adc8-4acd-8393-78473eaa8138", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-699623586834062230296181", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "73849201-61", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:7d83fe06-34cd-4cb3-b354-1cc16965c95e", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:5d403d5d-adc8-4acd-8393-78473eaa8138-urn:uuid:7d83fe06-34cd-4cb3-b354-1cc16965c95e/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:868df1e0-b497-4059-a0d3-3899630020da", - "idShort": "Indicator left_BPNL00000003AYRE_NO-263034085930445959442824", - "identification": "urn:uuid:aec68fca-ff25-433e-8816-dc2a74a3d0e2", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-263034085930445959442824", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "20125432-59", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:56922085-f377-46c0-88d3-4410431dbfd4", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:aec68fca-ff25-433e-8816-dc2a74a3d0e2-urn:uuid:56922085-f377-46c0-88d3-4410431dbfd4/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:087372bf-e9f0-49e6-98e8-0ce98140605c", - "idShort": "AC compressor_BPNL00000003AYRE_NO-990379220039630047372226", - "identification": "urn:uuid:030d6ff6-1410-4600-a072-ba10e658e067", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-990379220039630047372226", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "57929013-09", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:ee9c7192-5a31-4e37-9e21-9e10e689c833", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:030d6ff6-1410-4600-a072-ba10e658e067-urn:uuid:ee9c7192-5a31-4e37-9e21-9e10e689c833/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:f3f33eec-d7ed-44b5-8cf2-752b6c2c22ca", - "idShort": "Fender left_BPNL00000003AYRE_NO-825143168313832059901794", - "identification": "urn:uuid:c0fecc9b-0d9e-4482-b873-fa4205b5a51b", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-825143168313832059901794", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "13769860-47", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:7f7112ca-74b8-46ef-9950-08f1b42de144", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:c0fecc9b-0d9e-4482-b873-fa4205b5a51b-urn:uuid:7f7112ca-74b8-46ef-9950-08f1b42de144/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:6c2f4f41-3924-43da-969d-1afee232194c", - "idShort": "Door r-r_BPNL00000003AYRE_NO-521971425971587921853227", - "identification": "urn:uuid:2dfce279-eebe-4ccb-aad1-18df9e5e73ef", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-521971425971587921853227", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "28673126-98", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:bbc75609-9643-4b3b-bf17-1757afdf83fd", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:2dfce279-eebe-4ccb-aad1-18df9e5e73ef-urn:uuid:bbc75609-9643-4b3b-bf17-1757afdf83fd/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:d3ab8367-7d12-4991-bd89-cea117a62297", - "idShort": "Chassis_BPNL00000003AYRE_NO-446614016547833512657672", - "identification": "urn:uuid:4db46718-42cc-4e5d-a801-ce4409a50933", - "specificAssetIds": [ - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "00871379-44", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-446614016547833512657672", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:61f0f858-42a9-4f97-9d47-6e498042ec40", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:4db46718-42cc-4e5d-a801-ce4409a50933-urn:uuid:61f0f858-42a9-4f97-9d47-6e498042ec40/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:3940c819-dd95-422b-aa32-f7d3f1930de8", - "idShort": "Led headlight_BPNL00000003AYRE_NO-085197958619364691310676", - "identification": "urn:uuid:c9cc717e-89f1-4f31-9626-0c633e93e3b0", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "45415162-57", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-085197958619364691310676", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:593c2438-7023-4ab3-bcee-0499612f6afa", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:c9cc717e-89f1-4f31-9626-0c633e93e3b0-urn:uuid:593c2438-7023-4ab3-bcee-0499612f6afa/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:b32a9cda-b026-4715-b253-c08a7d11b840", - "idShort": "Differential Gear_BPNL00000003AYRE_NO-864558822157392253912002", - "identification": "urn:uuid:d7b8d6d5-b564-44e6-bc31-4b70f734362d", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-864558822157392253912002", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "32494586-73", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:0d99f1f4-be57-40df-83ca-5dc294410fa1", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:d7b8d6d5-b564-44e6-bc31-4b70f734362d-urn:uuid:0d99f1f4-be57-40df-83ca-5dc294410fa1/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:0391b219-9bf5-44cc-89af-e5db399a7d21", - "idShort": "Engine hood_BPNL00000003AYRE_NO-090051782799791537084391", - "identification": "urn:uuid:6efe2455-8927-4bab-9fe6-f71b911a04e4", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-090051782799791537084391", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "94421589-82", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:c9dd6460-c83e-453c-821e-3e95f1f50837", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:6efe2455-8927-4bab-9fe6-f71b911a04e4-urn:uuid:c9dd6460-c83e-453c-821e-3e95f1f50837/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:18f9d1ef-100e-4c0a-bb83-a407c644fcba", - "idShort": "Exterior mirror right_BPNL00000003AYRE_NO-653368825144313419172899", - "identification": "urn:uuid:5e2e6ae0-bcb1-487e-9133-937010263c77", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-653368825144313419172899", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "58471477-24", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:62d03e40-dfa0-411e-a230-3fac6ed8786e", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:5e2e6ae0-bcb1-487e-9133-937010263c77-urn:uuid:62d03e40-dfa0-411e-a230-3fac6ed8786e/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:7b2484b1-21b5-434a-aa2b-fd5434cc7ae3", - "idShort": "Turbocharger_BPNL00000003AYRE_NO-517977969951032933278547", - "identification": "urn:uuid:d2400778-0674-4b8a-a855-c5e54cb21643", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-517977969951032933278547", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "67034319-44", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:6c40e92d-0598-469b-b281-565cfbe462d8", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:d2400778-0674-4b8a-a855-c5e54cb21643-urn:uuid:6c40e92d-0598-469b-b281-565cfbe462d8/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:8fc21b03-1796-44af-bd52-c6998bfae1bb", - "idShort": "Taillight rear_BPNL00000003AYRE_NO-031797809646836273892252", - "identification": "urn:uuid:f806d532-2446-4717-a087-cd77037ce638", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-031797809646836273892252", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "61184040-23", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:57e14eba-eac3-4266-b38a-38bf9bde35de", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:f806d532-2446-4717-a087-cd77037ce638-urn:uuid:57e14eba-eac3-4266-b38a-38bf9bde35de/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:09c48e81-5c73-4e1a-85a2-0bf2fff4dada", - "idShort": "Door r-l_BPNL00000003AYRE_NO-357763365151675109323378", - "identification": "urn:uuid:7d8e404a-5fec-4942-884e-32d5883218a4", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-357763365151675109323378", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "15635759-16", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:cefc374b-ebeb-4238-89e3-2f3b18bae8f8", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:7d8e404a-5fec-4942-884e-32d5883218a4-urn:uuid:cefc374b-ebeb-4238-89e3-2f3b18bae8f8/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:643ff5cd-4468-4041-95d9-eeb2cc42c487", - "idShort": "Alternator_BPNL00000003AYRE_NO-234089394021291722206714", - "identification": "urn:uuid:85c0a681-278c-4653-8e31-a7c1c4c874d0", - "specificAssetIds": [ - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "81324139-23", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-234089394021291722206714", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:133dd88f-31be-4cbb-a8b8-ec19654fe95b", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:85c0a681-278c-4653-8e31-a7c1c4c874d0-urn:uuid:133dd88f-31be-4cbb-a8b8-ec19654fe95b/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:ad1f1627-a57e-481f-9cff-10990674f486", - "idShort": "Bumper front_BPNL00000003AYRE_NO-515224917844344882891590", - "identification": "urn:uuid:ba5dacdf-5f43-4ea6-a901-ade29e7371eb", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "54165444-59", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-515224917844344882891590", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:a017f0cf-7e81-4977-ad0c-1c18afb7611c", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:ba5dacdf-5f43-4ea6-a901-ade29e7371eb-urn:uuid:a017f0cf-7e81-4977-ad0c-1c18afb7611c/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:e35161e7-803d-4746-9528-db9d9814c692", - "idShort": "Fender right_BPNL00000003AYRE_NO-531750159545557912983689", - "identification": "urn:uuid:70ab8baf-eb87-47df-818b-e8f68fbf3ca9", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-531750159545557912983689", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "36643162-35", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:0a3bc1cc-eb6b-4715-9212-45a2791b938e", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:70ab8baf-eb87-47df-818b-e8f68fbf3ca9-urn:uuid:0a3bc1cc-eb6b-4715-9212-45a2791b938e/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:fbd019cc-6eda-4416-a668-e5d83271781d", - "idShort": "Trailer coupling_BPNL00000003AYRE_NO-741711117019957866246192", - "identification": "urn:uuid:f2af6d36-a18f-4361-82dd-2574d0060911", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-741711117019957866246192", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "09002013-68", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:3702e0dc-908d-4a5a-92bb-9578d849eee5", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:f2af6d36-a18f-4361-82dd-2574d0060911-urn:uuid:3702e0dc-908d-4a5a-92bb-9578d849eee5/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:00ce78e5-3bf3-4847-92c1-28771e83f998", - "idShort": "Tires_BPNL00000003AYRE_NO-383688996769055538071023", - "identification": "urn:uuid:b2e7eef0-0114-4fe8-bbdf-31a93a70f46b", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-383688996769055538071023", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "45863316-60", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:092c6537-0e92-4ad4-b79b-c3c4f3f3bdc7", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:b2e7eef0-0114-4fe8-bbdf-31a93a70f46b-urn:uuid:092c6537-0e92-4ad4-b79b-c3c4f3f3bdc7/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:5c48d1a1-bc81-4a9d-84db-53fc9c69e677", - "idShort": "Engine_BPNL00000003AYRE_NO-498815506813804675206852", - "identification": "urn:uuid:7b6f3b9d-1c43-4faa-985c-541231e4f4c2", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "10030939-59", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-498815506813804675206852", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:f3a5401b-db30-46ec-a3c8-ca676a0df69a", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:7b6f3b9d-1c43-4faa-985c-541231e4f4c2-urn:uuid:f3a5401b-db30-46ec-a3c8-ca676a0df69a/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:8c21d03f-b15e-427f-9cd0-c23701ed8f9c", - "idShort": "Tailgate_BPNL00000003AYRE_NO-623352977854691316219658", - "identification": "urn:uuid:99887bc8-7e62-4b22-b01f-c7ec4a6242bc", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-623352977854691316219658", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "85023955-75", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:95347a8d-6541-4b83-99a0-119499e84c41", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:99887bc8-7e62-4b22-b01f-c7ec4a6242bc-urn:uuid:95347a8d-6541-4b83-99a0-119499e84c41/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:98484975-27ed-4c79-ad94-c82c2c0360e8", - "idShort": "Dashboard_BPNL00000003AYRE_NO-775119044565305680099730", - "identification": "urn:uuid:900ed7b7-1f3b-4051-83c1-f778cbf9ed9e", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-775119044565305680099730", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "43501996-98", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:72719cdf-5def-41f4-8f2e-b69435c423df", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:900ed7b7-1f3b-4051-83c1-f778cbf9ed9e-urn:uuid:72719cdf-5def-41f4-8f2e-b69435c423df/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:e83c5a1f-7558-4978-990f-9c796224ce63", - "idShort": "Axle part front_BPNL00000003AYRE_NO-619372392092235773470630", - "identification": "urn:uuid:396d4426-0982-49be-a0eb-efbd0fec2722", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "12093297-03", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-619372392092235773470630", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:eb18c41a-1548-4c3d-a12a-eb1f9fff1262", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:396d4426-0982-49be-a0eb-efbd0fec2722-urn:uuid:eb18c41a-1548-4c3d-a12a-eb1f9fff1262/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:f1f14335-bda1-4dea-8818-0a1042bef7af", - "idShort": "Vehicle Combustion_BPNL00000003AYRE_OMAXALZZSKVQLDQYW", - "identification": "urn:uuid:b8d4f010-ab02-4e98-9ca6-b760846ac837", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "OMAXALZZSKVQLDQYW", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "van", - "subjectId": null, - "value": "OMAXALZZSKVQLDQYW", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "CW-84", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:71b71999-092d-4938-9eee-ada6085cea89", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:b8d4f010-ab02-4e98-9ca6-b760846ac837-urn:uuid:71b71999-092d-4938-9eee-ada6085cea89/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:b995cae9-0196-4ab5-b768-798eda1b5726", - "idShort": "Indicator right_BPNL00000003AYRE_NO-704843739978034957603374", - "identification": "urn:uuid:8b50909d-91ef-4797-ac19-fa27bd53bde3", - "specificAssetIds": [ - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "19073706-76", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-704843739978034957603374", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:ae249a4e-e14a-470b-bb87-0667aa729ebd", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:8b50909d-91ef-4797-ac19-fa27bd53bde3-urn:uuid:ae249a4e-e14a-470b-bb87-0667aa729ebd/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:867a1fd9-6a8b-4f56-a5ef-2b61f12f8bce", - "idShort": "Starter motor_BPNL00000003AYRE_NO-308048468179502884344759", - "identification": "urn:uuid:f50b671f-2dfa-4145-af1e-36eec69b46f2", - "specificAssetIds": [ - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "78141846-87", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-308048468179502884344759", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:6835125a-6575-4f46-b1e0-1e0eb4a91ce6", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:f50b671f-2dfa-4145-af1e-36eec69b46f2-urn:uuid:6835125a-6575-4f46-b1e0-1e0eb4a91ce6/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:d7d61cd8-27b2-4707-a4c2-d735cf1a0dcd", - "idShort": "Steering wheel_BPNL00000003AYRE_NO-940456958374895706746333", - "identification": "urn:uuid:65be17f0-3ef1-4ae8-b8e4-a00cf728c6d2", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-940456958374895706746333", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "77795937-13", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:6fd2cb7f-9350-48b8-b91e-c74b8d024f5f", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:65be17f0-3ef1-4ae8-b8e4-a00cf728c6d2-urn:uuid:6fd2cb7f-9350-48b8-b91e-c74b8d024f5f/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:a9d00358-d560-4a4b-98cc-d3a4d61c253b", - "idShort": "Axle part rear_BPNL00000003AYRE_NO-679687669439872282538739", - "identification": "urn:uuid:ac449c62-f160-42e6-b22e-054a3d9bb84f", - "specificAssetIds": [ - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "88111709-49", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AYRE", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-679687669439872282538739", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:53814187-0f3d-40b0-8499-73cb99136086", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://connector.cx-rel.edc.aws.bmw.cloud/BPNL00000003AYRE/urn:uuid:ac449c62-f160-42e6-b22e-054a3d9bb84f-urn:uuid:53814187-0f3d-40b0-8499-73cb99136086/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1-SNAPSHOT"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:9abcceb1-02ed-4384-83bb-e3f5b3be77d4", - "idShort": "urn:uuid:9abcceb1-02ed-4384-83bb-e3f5b3be77d4", - "identification": "urn:uuid:58a1b78a-e3cb-4bc9-b3a6-e5bdb3caba3b", - "specificAssetIds": [ - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003B5MJ", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-642718300945788010337776", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "62986Q6-51", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:3dd5a4eb-ba85-499b-8f3a-1de84014d82e", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "http://cxdev.germanywestcentral.cloudapp.azure.com:8185/BPNL00000003B5MJ/urn:uuid:58a1b78a-e3cb-4bc9-b3a6-e5bdb3caba3b-urn:uuid:3dd5a4eb-ba85-499b-8f3a-1de84014d82e/submodel?content=value&extent=withBlobValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.0"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:816f9c8d-6ef1-439e-89de-ef1d34481c17", - "idShort": "urn:uuid:816f9c8d-6ef1-439e-89de-ef1d34481c17", - "identification": "urn:uuid:816f9c8d-6ef1-439e-89de-ef1d34481c17", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-583510554514808021817274", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003AXS3", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "33906S3-94", - "semanticId": null - } - ], - "submodelDescriptors": [] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:68766b51-f794-4033-bcb1-f635ed309cd4", - "idShort": "BPNL00000003B3NX_urn:uuid:68766b51-f794-4033-bcb1-f635ed309cd4", - "identification": "urn:uuid:62040498-b48c-40ee-9522-dded6d10cff7", - "specificAssetIds": [ - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-171010696449453874507690", - "semanticId": null - }, - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "80833J7-99", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003B3NX", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "SerialPartTypization", - "id": "urn:uuid:18910f3e-f051-48e7-bd7a-b61d2e9c420e", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://subtier-a-edc-ocp0900009.apps.c7von4sy.westeurope.aroapp.io/BPNL00000003B3NX/urn:uuid:68766b51-f794-4033-bcb1-f635ed309cd4-urn:uuid:18910f3e-f051-48e7-bd7a-b61d2e9c420e/submodel?content=value&extent=withBlobValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "SUBMODEL-1.0RC02" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:50edf0fc-95a7-40b3-ad43-9156f3b81768", - "idShort": "urn:uuid:50edf0fc-95a7-40b3-ad43-9156f3b81768", - "identification": "urn:uuid:50edf0fc-95a7-40b3-ad43-9156f3b81768", - "specificAssetIds": [ - { - "key": "PartInstanceID", - "subjectId": null, - "value": "NO-456254855447445013274141", - "semanticId": null - }, - { - "key": "ManufacturerID", - "subjectId": null, - "value": "BPNL00000003CSGV", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:ea2193b6-72e4-439b-9e7f-f582eb20f56d", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://catenax.apps.demonstrator.dps.oncite.cloud/BPNL00000003CSGV/urn:uuid:50edf0fc-95a7-40b3-ad43-9156f3b81768-urn:uuid:ea2193b6-72e4-439b-9e7f-f582eb20f56d/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.1.1"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:f2e6d0cd-5a9a-451d-a583-510c5209f64c", - "idShort": "urn:uuid:f2e6d0cd-5a9a-451d-a583-510c5209f64c", - "identification": "urn:uuid:f2e6d0cd-5a9a-451d-a583-510c5209f64c", - "specificAssetIds": [ - { - "key": "ManufacturerID", - "subjectId": null, - "value": "BPNL00000003CSGV", - "semanticId": null - }, - { - "key": "PartInstanceID", - "subjectId": null, - "value": "NO-119744543834558117845033", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "serialPartTypization", - "id": "urn:uuid:618b1b64-72e4-439b-9e7f-f582eb20f56d", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://catenax.apps.demonstrator.dps.oncite.cloud/BPNL00000003CSGV/urn:uuid:f2e6d0cd-5a9a-451d-a583-510c5209f64c-urn:uuid:618b1b64-72e4-439b-9e7f-f582eb20f56d/submodel?content=value&extent=WithBLOBValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.1.1"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "EDC" - } - ] - } - ] - }, - { - "administration": null, - "description": [], - "globalAssetId": "urn:uuid:57ced45e-4f98-4aa4-bb67-6f7c51b90a4b", - "idShort": "BPNL00000003B2OM_urn:uuid:57ced45e-4f98-4aa4-bb67-6f7c51b90a4b", - "identification": "urn:uuid:af3a3d93-124b-42c1-9c4e-e1078840fbba", - "specificAssetIds": [ - { - "key": "manufacturerPartId", - "subjectId": null, - "value": "1O222E8-43", - "semanticId": null - }, - { - "key": "partInstanceId", - "subjectId": null, - "value": "NO-262893229514686288047560", - "semanticId": null - }, - { - "key": "manufacturerId", - "subjectId": null, - "value": "BPNL00000003B2OM", - "semanticId": null - } - ], - "submodelDescriptors": [ - { - "administration": null, - "description": [], - "idShort": "SerialPartTypization", - "id": "urn:uuid:9926c5c1-81e4-44bb-b2c1-7fed197777de", - "semanticId": { - "value": [ - "urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization" - ] - }, - "endpoints": [ - { - "protocolInformation": { - "href": "https://edc-ocp0900009.apps.c7von4sy.westeurope.aroapp.io/BPNL00000003B2OM/urn:uuid:57ced45e-4f98-4aa4-bb67-6f7c51b90a4b-urn:uuid:9926c5c1-81e4-44bb-b2c1-7fed197777de/submodel?content=value&extent=withBlobValue", - "endpointProtocol": "IDS/ECLIPSE DATASPACE CONNECTOR", - "endpointProtocolVersion": ["0.0.1"], - "subprotocol": null, - "subprotocolBody": null, - "subprotocolBodyEncoding": null - }, - "interface": "SUBMODEL-1.0RC02" - } - ] - } - ] - } - ], - "tombstones": [ - { - "catenaXId": "urn:uuid:68c9b1bf-b2c1-456a-883c-2aac5f5cb5f4", - "endpointURL": null, - "processingError": { - "processStep": "DigitalTwinRequest", - "errorDetail": "404 : \"{\"error\":{\"message\":\"Shell for identifier urn:uuid:68c9b1bf-b2c1-456a-883c-2aac5f5cb5f4 not found\",\"path\":\"/registry/registry/shell-descriptors/urn:uuid:68c9b1bf-b2c1-456a-883c-2aac5f5cb5f4\",\"details\":{}}}\"", - "lastAttempt": "2022-11-08T08:37:18.724490618Z", - "retryCounter": 3 - } - }, - { - "catenaXId": "urn:uuid:68c9b1bf-b2c1-456a-883c-2aac5f5cb5f4", - "endpointURL": null, - "processingError": { - "processStep": "BpdmRequest", - "errorDetail": "Cannot find ManufacturerId for CatenaXId: urn:uuid:68c9b1bf-b2c1-456a-883c-2aac5f5cb5f4", - "lastAttempt": "2022-11-08T08:37:18.724609316Z", - "retryCounter": 0 - } - }, - { - "catenaXId": "urn:uuid:68766b51-f794-4033-bcb1-f635ed309cd4", - "endpointURL": "https://subtier-a-edc-ocp0900009.apps.c7von4sy.westeurope.aroapp.io/BPNL00000003B3NX/urn:uuid:68766b51-f794-4033-bcb1-f635ed309cd4-urn:uuid:f6f15bca-c901-4dca-bdb0-6abea6fae5fd/submodel?content=value&extent=withBlobValue", - "processingError": { - "processStep": "SubmodelRequest", - "errorDetail": "400 Bad Request: \"HTTP ERROR 400 Bad RequestURI: /api/service/urn:uuid:68766b51-f794-4033-bcb1-f635ed309cd4-urn:uuid:f6f15bca-c901-4dca-bdb0-6abea6fae5fd/submodelSTATUS: 400MESSAGE: Bad RequestSERVLET: EDC-default\"", - "lastAttempt": "2022-11-08T08:38:48.752558896Z", - "retryCounter": 3 - } - }, - { - "catenaXId": "urn:uuid:50edf0fc-95a7-40b3-ad43-9156f3b81768", - "endpointURL": "https://catenax.apps.demonstrator.dps.oncite.cloud/BPNL00000003CSGV/urn:uuid:50edf0fc-95a7-40b3-ad43-9156f3b81768-urn:uuid:53ab1d7d-3f1d-4caa-9733-83c3e9b3bd64/submodel?content=value&extent=WithBLOBValue", - "processingError": { - "processStep": "SubmodelRequest", - "errorDetail": "500 Server Error: \"HTTP ERROR 500 Internal Server ErrorURI: /api/service/urn:uuid:50edf0fc-95a7-40b3-ad43-9156f3b81768-urn:uuid:53ab1d7d-3f1d-4caa-9733-83c3e9b3bd64/submodelSTATUS: 500MESSAGE: Internal Server ErrorSERVLET: EDC-default\"", - "lastAttempt": "2022-11-08T08:39:19.657273145Z", - "retryCounter": 3 - } - }, - { - "catenaXId": "urn:uuid:f2e6d0cd-5a9a-451d-a583-510c5209f64c", - "endpointURL": "https://catenax.apps.demonstrator.dps.oncite.cloud/BPNL00000003CSGV/urn:uuid:f2e6d0cd-5a9a-451d-a583-510c5209f64c-urn:uuid:4ca5b6eb-3f1d-4caa-9733-83c3e9b3bd64/submodel?content=value&extent=WithBLOBValue", - "processingError": { - "processStep": "SubmodelRequest", - "errorDetail": "500 Server Error: \"HTTP ERROR 500 Internal Server ErrorURI: /api/service/urn:uuid:f2e6d0cd-5a9a-451d-a583-510c5209f64c-urn:uuid:4ca5b6eb-3f1d-4caa-9733-83c3e9b3bd64/submodelSTATUS: 500MESSAGE: Internal Server ErrorSERVLET: EDC-default\"", - "lastAttempt": "2022-11-08T08:39:45.123118683Z", - "retryCounter": 3 - } - }, - { - "catenaXId": "urn:uuid:57ced45e-4f98-4aa4-bb67-6f7c51b90a4b", - "endpointURL": "https://edc-ocp0900009.apps.c7von4sy.westeurope.aroapp.io/BPNL00000003B2OM/urn:uuid:57ced45e-4f98-4aa4-bb67-6f7c51b90a4b-urn:uuid:39c6697f-cf4b-427e-8520-dbf0918020ec/submodel?content=value&extent=withBlobValue", - "processingError": { - "processStep": "SubmodelRequest", - "errorDetail": "500 Server Error: \"HTTP ERROR 500 Internal Server ErrorURI: /api/service/urn:uuid:57ced45e-4f98-4aa4-bb67-6f7c51b90a4b-urn:uuid:39c6697f-cf4b-427e-8520-dbf0918020ec/submodelSTATUS: 500MESSAGE: Internal Server ErrorSERVLET: EDC-default\"", - "lastAttempt": "2022-11-08T08:40:41.572188451Z", - "retryCounter": 3 - } - } - ], - "submodels": [], - "bpns": [ - { - "manufacturerId": "BPNL00000003B2OM", - "manufacturerName": "Tier A" - }, - { - "manufacturerId": "BPNL00000003B5MJ", - "manufacturerName": "Tier B" - }, - { - "manufacturerId": "BPNL00000003CSGV", - "manufacturerName": "TEST_BPN_TIER_3" - }, - { - "manufacturerId": "BPNL00000003AXS3", - "manufacturerName": "Sub Tier B" - }, - { - "manufacturerId": "BPNL00000003B3NX", - "manufacturerName": "Sub Tier A" - }, - { - "manufacturerId": "BPNL00000003AYRE", - "manufacturerName": "OEM A" - } - ] -} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/semantichub/all-models-page-IT.json b/irs-api/src/test/resources/__files/semantichub/all-models-page-IT.json new file mode 100644 index 0000000000..36d2bd6a4a --- /dev/null +++ b/irs-api/src/test/resources/__files/semantichub/all-models-page-IT.json @@ -0,0 +1,554 @@ +{ + "items": [ + { + "urn": "urn:bamm:io.catenax.asset_tracker_links:1.0.0#AssetTrackerLinks", + "version": "1.0.0", + "name": "AssetTrackerLinks", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.battery.battery_pass:3.0.1#BatteryPass", + "version": "3.0.1", + "name": "BatteryPass", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.battery.product_description:1.0.1#ProductDescription", + "version": "1.0.1", + "name": "ProductDescription", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.bom_as_specified:1.0.0#BomAsSpecified", + "version": "1.0.0", + "name": "BomAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.bom_as_specified:1.0.1#BomAsSpecified", + "version": "1.0.1", + "name": "BomAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.certificate_of_destruction:1.0.1#CertificateOfDestruction", + "version": "1.0.1", + "name": "CertificateOfDestruction", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.classified_load_spectrum:1.0.0#ClassifiedLoadSpectrum", + "version": "1.0.0", + "name": "ClassifiedLoadSpectrum", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.decomissioning_certificate:1.0.0#DecommissioningCertificate", + "version": "1.0.0", + "name": "DecommissioningCertificate", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.eol_story:1.0.0#EndOfLife", + "version": "1.0.0", + "name": "EndOfLife", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.fleet.claim_data:1.0.0#ClaimData", + "version": "1.0.0", + "name": "ClaimData", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.fleet.diagnostic_data:1.0.0#DiagnosticData", + "version": "1.0.0", + "name": "DiagnosticData", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.idconversion:1.0.0#IdConversion", + "version": "1.0.0", + "name": "IdConversion", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.individual_asset_definition:1.0.0#IndividualAssetDefinition", + "version": "1.0.0", + "name": "IndividualAssetDefinition", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.iot_sensor_data:1.0.0#IotSensorData", + "version": "1.0.0", + "name": "IotSensorData", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.iot_sensor_device_definition:1.0.0#IotSensorDeviceDefinition", + "version": "1.0.0", + "name": "IotSensorDeviceDefinition", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.manufactured_parts_quality_information:1.0.0#ManufacturedPartsQualityInformation", + "version": "1.0.0", + "name": "ManufacturedPartsQualityInformation", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.manufacturing_capability:1.0.0#ManufacturingCapability", + "version": "1.0.0", + "name": "ManufacturingCapability", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.market_place_offer:1.2.0#MarketplaceOffer", + "version": "1.2.0", + "name": "MarketplaceOffer", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.market_place_offer:1.4.0#MarketplaceOffer", + "version": "1.4.0", + "name": "MarketplaceOffer", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.material_flow_simulation_result:1.0.0#MaterialFlowSimulationResult", + "version": "1.0.0", + "name": "MaterialFlowSimulationResult", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.material_for_homologation:1.0.0#MaterialForHomologation", + "version": "1.0.0", + "name": "MaterialForHomologation", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.part_as_specified:1.0.0#PartAsSpecified", + "version": "1.0.0", + "name": "PartAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.part_as_specified:1.0.1#PartAsSpecified", + "version": "1.0.1", + "name": "PartAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.part_as_specified:2.0.0#PartAsSpecified", + "version": "2.0.0", + "name": "PartAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.pcf:2.0.0#Pcf", + "version": "2.0.0", + "name": "Pcf", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.pcf:3.0.0#Pcf", + "version": "3.0.0", + "name": "Pcf", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.pcf:4.0.0#Pcf", + "version": "4.0.0", + "name": "Pcf", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.pcf:4.0.1#Pcf", + "version": "4.0.1", + "name": "Pcf", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.product_stock:1.0.0#ProductStock", + "version": "1.0.0", + "name": "ProductStock", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.quality_task:1.0.0#QualityTask", + "version": "1.0.0", + "name": "QualityTask", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.refurbishing_certificate:1.0.0#RefurbishingCertificate", + "version": "1.0.0", + "name": "RefurbishingCertificate", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.remanufacturing_certificate:1.0.0#RemanufacturingCertificate", + "version": "1.0.0", + "name": "RemanufacturingCertificate", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.return_request:1.1.1#ReturnRequest", + "version": "1.1.1", + "name": "ReturnRequest", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.return_request:1.1.2#ReturnRequest", + "version": "1.1.2", + "name": "ReturnRequest", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.reuse_certificate:1.0.0#ReuseCertificate", + "version": "1.0.0", + "name": "ReuseCertificate", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.rul:1.0.0#RemainingUsefulLife", + "version": "1.0.0", + "name": "RemainingUsefulLife", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.sealant.sealant_pass:1.0.0#SealantPass", + "version": "1.0.0", + "name": "SealantPass", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.address_characteristic:1.0.1#AddressAspect", + "version": "1.0.1", + "name": "AddressAspect", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.address_characteristic:2.0.0#AddressAspect", + "version": "2.0.0", + "name": "AddressAspect", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.contact_information:1.0.0#ContactInformation", + "version": "1.0.0", + "name": "ContactInformation", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.contact_information:2.0.0#ContactInformation", + "version": "2.0.0", + "name": "ContactInformation", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.physical_dimension:1.0.0#PhysicalDimensions", + "version": "1.0.0", + "name": "PhysicalDimensions", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.physical_dimension:2.0.0#PhysicalDimensions", + "version": "2.0.0", + "name": "PhysicalDimensions", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.recycling_strategy_certificate:1.0.0#RecyclingStrategyCertificate", + "version": "1.0.0", + "name": "RecyclingStrategyCertificate", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.single_level_bom_as_specified:1.0.0#SingleLevelBomAsSpecified", + "version": "1.0.0", + "name": "SingleLevelBomAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.single_level_usage_as_planned:1.1.0#SingleLevelUsageAsPlanned", + "version": "1.1.0", + "name": "SingleLevelUsageAsPlanned", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.time_series_reference:1.0.0#TimeSeriesReference", + "version": "1.0.0", + "name": "TimeSeriesReference", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.traction_battery_code:1.0.0#TractionBatteryCode", + "version": "1.0.0", + "name": "TractionBatteryCode", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.user_estimated_loading:1.0.0#UserEstimatedLoading", + "version": "1.0.0", + "name": "UserEstimatedLoading", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.vehicle.product_description:1.0.0#ProductDescription", + "version": "1.0.0", + "name": "ProductDescription", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.vehicle.product_description:2.0.0#ProductDescription", + "version": "2.0.0", + "name": "ProductDescription", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.waste:1.0.0#Waste", + "version": "1.0.0", + "name": "Waste", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.week_based_capacity_group:1.0.1#WeekBasedCapacityGroup", + "version": "1.0.1", + "name": "WeekBasedCapacityGroup", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.week_based_material_demand:1.0.1#WeekBasedMaterialDemand", + "version": "1.0.1", + "name": "WeekBasedMaterialDemand", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.essincident:2.0.0#EssIncident", + "version": "2.0.0", + "name": "EssIncident", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.fleet.vehicles:1.0.0#Vehicles", + "version": "1.0.0", + "name": "Vehicles", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.id_based_comment:1.0.0#IdBasedComment", + "version": "1.0.0", + "name": "IdBasedComment", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.id_based_request_for_update:1.0.0#IdBasedRequestForUpdate", + "version": "1.0.0", + "name": "IdBasedRequestForUpdate", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.id_based_request_for_update:2.0.0#IdBasedRequestForUpdate", + "version": "2.0.0", + "name": "IdBasedRequestForUpdate", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.manufacturing_capability:2.0.0#ManufacturingCapability", + "version": "2.0.0", + "name": "ManufacturingCapability", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.material_flow_simulation_result:2.0.0#MaterialFlowSimulationResultAspect", + "version": "2.0.0", + "name": "MaterialFlowSimulationResultAspect", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.parts_analyses:2.0.0#PartsAnalyses", + "version": "2.0.0", + "name": "PartsAnalyses", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.planned_production_output:1.0.0#PlannedProductionOutput", + "version": "1.0.0", + "name": "PlannedProductionOutput", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.secondary_material_content:1.0.0#SecondaryMaterialContent", + "version": "1.0.0", + "name": "SecondaryMaterialContent", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.address_characteristic:3.0.0#AddressAspect", + "version": "3.0.0", + "name": "AddressAspect", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.business_partner_number:1.0.0#BusinessPartnerNumber", + "version": "1.0.0", + "name": "BusinessPartnerNumber", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.contact_information:3.0.0#ContactInformation", + "version": "3.0.0", + "name": "ContactInformation", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.part_site_information_as_built:1.0.0#PartSiteInformationAsBuilt", + "version": "1.0.0", + "name": "PartSiteInformationAsBuilt", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.quantity:1.0.0#Quantity", + "version": "1.0.0", + "name": "Quantity", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.recycling_strategy_certificate:2.0.0#RecyclingStrategyCertificate", + "version": "2.0.0", + "name": "RecyclingStrategyCertificate", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.secondary_material_content:1.0.0#SecondaryMaterialContent", + "version": "1.0.0", + "name": "SecondaryMaterialContent", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.shopfloor_information_types:1.0.0#ShopfloorInformationTypes", + "version": "1.0.0", + "name": "ShopfloorInformationTypes", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.uuid:1.0.0#Uuid", + "version": "1.0.0", + "name": "Uuid", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.single_level_bom_as_specified:2.0.0#SingleLevelBomAsSpecified", + "version": "2.0.0", + "name": "SingleLevelBomAsSpecified", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.vehicle.product_description:3.0.0#ProductDescription", + "version": "3.0.0", + "name": "ProductDescription", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", + "version": "1.0.0", + "name": "SingleLevelBomAsBuilt", + "type": "BAMM", + "status": "DEPRECATED" + }, + { + "urn": "urn:samm:io.catenax.batch:2.0.0#Batch", + "version": "2.0.0", + "name": "Batch", + "type": "SAMM", + "status": "DEPRECATED" + }, + { + "urn": "urn:bamm:io.catenax.serial_part:1.0.1#SerialPart", + "version": "1.0.1", + "name": "SerialPart", + "type": "BAMM", + "status": "DEPRECATED" + } + ], + "totalItems": 78, + "currentPage": 0, + "totalPages": 1, + "itemCount": 78 +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/semantichub/batch-2.0.0-schema.json b/irs-api/src/test/resources/__files/semantichub/batch-2.0.0-schema.json new file mode 100644 index 0000000000..4ff694cd76 --- /dev/null +++ b/irs-api/src/test/resources/__files/semantichub/batch-2.0.0-schema.json @@ -0,0 +1,143 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "description": "A batch is a quantity of (semi-) finished products or (raw) material product that have been produced under the same circumstances (e.g. same production location), as specified groups or amounts, within a certain time frame. Every batch can differ in the number or amount of products. Different batches can have varied specifications, e.g., different colors. A batch is identified via a Batch ID.", + "type": "object", + "components": { + "schemas": { + "urn_samm_io.catenax.batch_2.0.0_CatenaXIdTrait": { + "type": "string", + "description": "The provided regular expression ensures that the UUID is composed of five groups of characters separated by hyphens, in the form 8-4-4-4-12 for a total of 36 characters (32 hexadecimal characters and 4 hyphens), optionally prefixed by \"urn:uuid:\" to make it an IRI.", + "pattern": "(^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)|(^urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)" + }, + "urn_samm_io.catenax.batch_2.0.0_KeyTrait": { + "type": "string", + "description": "Constraint that ensures that the predefined keys are used.", + "pattern": "^(manufacturerId|batchId)$" + }, + "urn_samm_io.catenax.batch_2.0.0_ValueCharacteristic": { + "type": "string", + "description": "The value of an identifier." + }, + "urn_samm_io.catenax.batch_2.0.0_KeyValueList": { + "description": "A list of key value pairs for local identifiers, which are composed of a key and a corresponding value.", + "type": "object", + "properties": { + "key": { + "description": "The key of a local identifier.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_KeyTrait" + }, + "value": { + "description": "The value of an identifier.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_ValueCharacteristic" + } + }, + "required": [ + "key", + "value" + ] + }, + "urn_samm_io.catenax.batch_2.0.0_LocalIdentifierCharacteristic": { + "description": "A batch may have multiple attributes, which uniquely identify that batch in a specific dataspace (e.g. the manufacturer`s dataspace)", + "type": "array", + "items": { + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_KeyValueList" + }, + "uniqueItems": true + }, + "urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Timestamp": { + "type": "string", + "pattern": "-?([1-9][0-9]{3,}|0[0-9]{3})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?|(24:00:00(\\.0+)?))(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?", + "description": "Describes a Property which contains the date and time with an optional timezone." + }, + "urn_samm_io.catenax.batch_2.0.0_ProductionCountryCodeTrait": { + "type": "string", + "description": "Regular Expression that ensures a three-letter code", + "pattern": "^[A-Z]{3}$" + }, + "urn_samm_io.catenax.batch_2.0.0_ManufacturingCharacteristic": { + "description": "Characteristic to describe manufacturing related data", + "type": "object", + "properties": { + "date": { + "description": "Timestamp of the manufacturing date as the final step in production process (e.g. final quality check, ready-for-shipment event)", + "$ref": "#/components/schemas/urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Timestamp" + }, + "country": { + "description": "Country code where the part was manufactured", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_ProductionCountryCodeTrait" + } + }, + "required": [ + "date" + ] + }, + "urn_samm_io.catenax.batch_2.0.0_PartIdCharacteristic": { + "type": "string", + "description": "The part ID is a multi-character string, ususally assigned by an ERP system" + }, + "urn_samm_io.catenax.batch_2.0.0_PartNameCharacteristic": { + "type": "string", + "description": "Part Name in string format from the respective system in the value chain" + }, + "urn_samm_io.catenax.batch_2.0.0_ClassificationCharacteristic": { + "type": "string", + "description": "A part type must be placed into one of the following classes: 'component', 'product', 'software', 'assembly', 'tool', or 'raw material'.", + "enum": [ + "product", + "raw material", + "software", + "assembly", + "tool", + "component" + ] + }, + "urn_samm_io.catenax.batch_2.0.0_PartTypeInformationCharacteristic": { + "description": "The characteristics of the part type", + "type": "object", + "properties": { + "manufacturerPartId": { + "description": "Part ID as assigned by the manufacturer of the part. The Part ID identifies the part (as designed) in the manufacturer`s dataspace. The Part ID does not reference a specific instance of a part and thus should not be confused with the serial number or batch number.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_PartIdCharacteristic" + }, + "nameAtManufacturer": { + "description": "Name of the part as assigned by the manufacturer", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_PartNameCharacteristic" + }, + "classification": { + "description": "The classification of the part type according to STEP standard definition", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_ClassificationCharacteristic" + } + }, + "required": [ + "manufacturerPartId", + "nameAtManufacturer", + "classification" + ] + } + } + }, + "properties": { + "catenaXId": { + "description": "The fully anonymous Catena-X ID of the batch, valid for the Catena-X dataspace.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_CatenaXIdTrait" + }, + "localIdentifiers": { + "description": "A local identifier enables identification of a part in a specific dataspace, but is not unique in Catena-X dataspace. Multiple local identifiers may exist.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_LocalIdentifierCharacteristic" + }, + "manufacturingInformation": { + "description": "Information from manufacturing process, such as manufacturing date and manufacturing country", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_ManufacturingCharacteristic" + }, + "partTypeInformation": { + "description": "The part type of which the batch has been instantiated of.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_PartTypeInformationCharacteristic" + } + }, + "required": [ + "catenaXId", + "localIdentifiers", + "manufacturingInformation", + "partTypeInformation" + ] +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/semantichub/singleLevelBomAsBuilt-2.0.0-schema.json b/irs-api/src/test/resources/__files/semantichub/singleLevelBomAsBuilt-2.0.0-schema.json new file mode 100644 index 0000000000..70fbf22bb0 --- /dev/null +++ b/irs-api/src/test/resources/__files/semantichub/singleLevelBomAsBuilt-2.0.0-schema.json @@ -0,0 +1,102 @@ +{ + "$schema" : "http://json-schema.org/draft-04/schema", + "description" : "The single-level bill of material represents one sub-level of an assembly and does not include any lower-level subassemblies. The as-built lifecycle references all child items as manufactured by the manufacturer referencing only child items in an as-built lifecycle themselves (e.g. serial parts or batches), unless parts can only be tracked by an part ID (on a type level).\n\nIf it is unclear which item has been built-in into the parent item, all potential parts must be listed. This is the case when, e.g. the same item is supplied by two suppliers and the item is only tracked by a customer part ID during assembly, these items can not be differentiated from each other.\n", + "type" : "object", + "components" : { + "schemas" : { + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_CatenaXIdTraitCharacteristic" : { + "type" : "string", + "description" : "The provided regular expression ensures that the UUID is composed of five groups of characters separated by hyphens, in the form 8-4-4-4-12 for a total of 36 characters (32 hexadecimal characters and 4 hyphens), optionally prefixed by \"urn:uuid:\" to make it an IRI.", + "pattern" : "(^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)|(^urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)" + }, + "urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Timestamp" : { + "type" : "string", + "pattern" : "-?([1-9][0-9]{3,}|0[0-9]{3})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?|(24:00:00(\\.0+)?))(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?", + "description" : "Describes a Property which contains the date and time with an optional timezone." + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_NumberOfObjects" : { + "type" : "number", + "description" : "Quantifiable number of objects in reference to the measurementUnit" + }, + "urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_UnitReference" : { + "type" : "string", + "pattern" : "[a-zA-Z]*:[a-zA-Z]+", + "description" : "Describes a Property containing a reference to one of the units in the Unit Catalog." + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_QuantityCharacteristic" : { + "description" : "Describes the quantity in which the child item is assembled in the given parent item by providing a quantity value and the measurement unit in which the quantity is measured.", + "type" : "object", + "properties" : { + "quantityNumber" : { + "description" : "The number of objects related to the measurement unit", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_NumberOfObjects" + }, + "measurementUnit" : { + "description" : "Unit of Measurement for the quantity of serialized objects", + "$ref" : "#/components/schemas/urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_UnitReference" + } + }, + "required" : [ "quantityNumber", "measurementUnit" ] + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_BpnTrait" : { + "type" : "string", + "description" : "Business Partner Number Regular Expression allowing only BPNL which stands for a legal entity.", + "pattern" : "^(BPNL)([0-9]{8})([a-zA-Z0-9]{4})$" + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_HasAlternativesCharacteristic" : { + "type" : "boolean", + "description" : "Describes the value whether the child data has alternatives." + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_ChildData" : { + "description" : "Catena-X ID and meta data of the assembled child item.", + "type" : "object", + "properties" : { + "createdOn" : { + "description" : "Timestamp when the relation between the parent item and the child item was created, e.g. when the serialized child part was assembled into the given part.", + "$ref" : "#/components/schemas/urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Timestamp" + }, + "quantity" : { + "description" : "Quantity of which the child item is assembled into the parent item. In general it is '1' for serialized parts.", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_QuantityCharacteristic" + }, + "lastModifiedOn" : { + "description" : "Timestamp when the assembly relationship between parent item and child item was last modified.", + "$ref" : "#/components/schemas/urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Timestamp" + }, + "catenaXId" : { + "description" : "The Catena-X ID of the given part (e.g. the assembly), valid for the Catena-X dataspace.", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_CatenaXIdTraitCharacteristic" + }, + "businessPartner" : { + "description" : "The supplier of the given child item.", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_BpnTrait" + }, + "hasAlternatives" : { + "description" : "Expresses wether the part is built-in or wether it is one of several options. If the value is false, it can be assumend this exact item is built-in. If the value is true, it is unknown wether this or an alternative item is built-in.\nThis is the case when, e.g. the same item is supplied by two suppliers, the item is only tracked by a customer part ID during assembly. Thus, these items can not be differentiated from each other.\n\n", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_HasAlternativesCharacteristic" + } + }, + "required" : [ "createdOn", "quantity", "catenaXId", "businessPartner", "hasAlternatives" ] + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_SetOfChildItemsCharacteristic" : { + "description" : "Set of child items the parent item is assembled by (one structural level down).", + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_ChildData" + }, + "uniqueItems" : true + } + } + }, + "properties" : { + "catenaXId" : { + "description" : "The Catena-X ID of the given part (e.g. the assembly), valid for the Catena-X dataspace.", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_CatenaXIdTraitCharacteristic" + }, + "childItems" : { + "description" : "Set of child items, of which the given parent item is assembled by (one structural level down).", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_SetOfChildItemsCharacteristic" + } + }, + "required" : [ "catenaXId", "childItems" ] +} \ No newline at end of file diff --git a/irs-common/src/main/java/org/eclipse/tractusx/irs/common/util/concurrent/ResultFinder.java b/irs-common/src/main/java/org/eclipse/tractusx/irs/common/util/concurrent/ResultFinder.java new file mode 100644 index 0000000000..a8fd1fb069 --- /dev/null +++ b/irs-common/src/main/java/org/eclipse/tractusx/irs/common/util/concurrent/ResultFinder.java @@ -0,0 +1,150 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2024: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2024 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.irs.common.util.concurrent; + +import static java.util.concurrent.CompletableFuture.allOf; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; + +import lombok.Getter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * Helper class to find the relevant result from a list of futures. + */ +@Slf4j +public class ResultFinder { + + /** + * Returns a new {@link CompletableFuture} which completes + * when at least one of the given futures completes successfully or all fail. + * The result from the fastest successful future is returned. The others are ignored. + * + * @param futures the futures + * @param the return type + * @return a {@link CompletableFuture} returning the fastest successful result or empty + */ + public CompletableFuture getFastestResult(final List> futures) { + + if (futures == null || futures.isEmpty()) { + log.warn("Called getFastestResult with empty list of futures"); + return CompletableFuture.completedFuture(null); + } + + log.debug("Trying to get fastest result from list of futures"); + + // The purpose of this overall future is to track when the first data request is successful. + // This way we do not need to wait for the others to complete. + final CompletableFuture overallFuture = new CompletableFuture<>(); + + final List exceptions = new ArrayList<>(); + + final var futuresList = futures.stream() + .map(future -> future.exceptionally(collectingExceptionsAndThrow(exceptions)) + .handle(completingOnFirstSuccessful(overallFuture))) + .toList(); + + allOf(toArray(futuresList)).whenComplete((value, ex) -> { + + log.debug("All of the futures completed"); + + if (ex != null) { + log.warn("All failed: " + System.lineSeparator() // + + exceptions.stream() + .map(ExceptionUtils::getStackTrace) + .collect(Collectors.joining(System.lineSeparator())), ex); + + overallFuture.completeExceptionally(new CompletionExceptions("None successful", exceptions)); + } else { + overallFuture.complete(null); + } + }); + + return overallFuture; + } + + private static CompletableFuture[] toArray(final List> handledFutures) { + return handledFutures.toArray(new CompletableFuture[0]); + } + + private static BiFunction completingOnFirstSuccessful( + final CompletableFuture overallFuture) { + + return (value, throwable) -> { + + log.debug("value: '{}', throwable: {}", value, throwable); + + final boolean notFinishedByOtherFuture = !overallFuture.isDone(); + log.debug("notFinishedByOtherFuture {} ", notFinishedByOtherFuture); + + final boolean currentFutureSuccessful = throwable == null && value != null; + + if (notFinishedByOtherFuture && currentFutureSuccessful) { + + log.debug("First future that completed successfully"); + overallFuture.complete(value); + return true; + + } else { + if (throwable != null) { + log.warn("Exception occurred: " + throwable.getMessage(), throwable); + throw new CompletionException(throwable.getMessage(), throwable); + } + return false; + } + }; + } + + private static Function collectingExceptionsAndThrow(final List exceptions) { + return t -> { + log.error("Exception occurred: " + t.getMessage(), t); + exceptions.add(t); + throw new CompletionException(t); + }; + } + + /** + * Helper exception that can hold multiple causes. + */ + @Getter + @ToString + public static class CompletionExceptions extends CompletionException { + + private final List causes; + + public CompletionExceptions(final String msg, final List causes) { + super(msg); + this.causes = causes; + } + + } +} diff --git a/irs-common/src/test/java/org/eclipse/tractusx/irs/common/util/concurrent/ResultFinderTest.java b/irs-common/src/test/java/org/eclipse/tractusx/irs/common/util/concurrent/ResultFinderTest.java new file mode 100644 index 0000000000..90c783f276 --- /dev/null +++ b/irs-common/src/test/java/org/eclipse/tractusx/irs/common/util/concurrent/ResultFinderTest.java @@ -0,0 +1,162 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2024: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2024 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.irs.common.util.concurrent; + +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; + +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +/** + * Test for {@link ResultFinder} + */ +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@DisplayName("ResultFinder") +class ResultFinderTest { + + final ResultFinder sut = new ResultFinder(); + + @NullAndEmptySource + @ParameterizedTest + void withNullOrEmptyInputList_nullShouldBeReturned(final List> list) + throws ExecutionException, InterruptedException { + final var result = sut.getFastestResult(list).get(); + assertThat(result).isNull(); + } + + @Test + void withOneSuccessfulCompletableFuture_theSuccessfulResultShouldBeReturned() + throws ExecutionException, InterruptedException { + final var futures = List.of(supplyAsync(() -> "ok")); + final String result = sut.getFastestResult(futures).get(); + assertThat(result).isEqualTo("ok"); + } + + @Test + void withOnlyOneSuccessful_andOtherCompletableFuturesFailing_theSuccessfulResultShouldBeReturned() + throws ExecutionException, InterruptedException { + + // given + final List> futures = List.of( // + supplyAsync(() -> { + throw new RuntimeException("failing"); + }), // + supplyAsync(() -> "ok"), // + supplyAsync(() -> { + throw new RuntimeException("failing"); + })); + + // when + final String result = sut.getFastestResult(futures).get(); + + // then + assertThat(result).isEqualTo("ok"); + } + + @Test + void withAllCompletableFuturesFailing_itShouldThrow() { + + // given + final List> futures = List.of( // + futureThrowAfterMillis(5000, () -> new RuntimeException("failing 1")), // + supplyAsync(() -> { + throw new RuntimeException("failing 2"); + }), // + futureThrowAfterMillis(1000, () -> new RuntimeException("failing 3"))); + + // when + final ThrowingCallable call = () -> sut.getFastestResult(futures).get(); + + // then + assertThatThrownBy(call).isInstanceOf(ExecutionException.class) + .extracting(Throwable::getCause) + .isInstanceOf(ResultFinder.CompletionExceptions.class) + .extracting(collectedFailures -> (ResultFinder.CompletionExceptions) collectedFailures) + .extracting(ResultFinder.CompletionExceptions::getCauses) + .describedAs("should have collected all exceptions") + .satisfies(causes -> assertThat( + causes.stream().map(Throwable::getMessage).toList()).containsExactlyInAnyOrder( + "java.lang.RuntimeException: failing 1", + "java.lang.RuntimeException: failing 2", + "java.lang.RuntimeException: failing 3")); + + } + + @Test + void withMultipleSuccessfulCompletableFutures_theFastestSuccessfulResultShouldBeReturned() + throws ExecutionException, InterruptedException { + + // given + final List> futures = List.of( // + futureThrowAfterMillis(200, () -> new RuntimeException("slower failing")), // + futureReturnAfterMillis(1000, () -> "slowest success"), // + futureReturnAfterMillis(300, () -> "fastest success"), // + futureThrowAfterMillis(0, () -> new RuntimeException("failing immediately")) // + ); + + // when + final String result = sut.getFastestResult(futures).get(); + + // then + assertThat(result).isEqualTo("fastest success"); + } + + private static CompletableFuture futureThrowAfterMillis(final int sleepMillis, + final Supplier exceptionSupplier) { + return supplyAsync(() -> { + sleep(sleepMillis); + throw exceptionSupplier.get(); + }); + } + + private static CompletableFuture futureReturnAfterMillis(final int sleepMillis, + final Supplier resultSupplier) { + return supplyAsync(() -> { + sleep(sleepMillis); + return resultSupplier.get(); + }); + } + + private static void sleep(final int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file diff --git a/irs-edc-client/pom.xml b/irs-edc-client/pom.xml index 10d3049c1e..a767cde24d 100644 --- a/irs-edc-client/pom.xml +++ b/irs-edc-client/pom.xml @@ -168,6 +168,11 @@ org.eclipse.edc ${edc.version} + + org.eclipse.tractusx.irs + irs-common + ${revision} + org.eclipse.tractusx.irs diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/ContractNegotiationService.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/ContractNegotiationService.java index 545f14e830..63def1d591 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/ContractNegotiationService.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/ContractNegotiationService.java @@ -62,12 +62,6 @@ public class ContractNegotiationService { private final PolicyCheckerService policyCheckerService; private final EdcConfiguration config; - public NegotiationResponse negotiate(final String providerConnectorUrl, final CatalogItem catalogItem) - throws ContractNegotiationException, UsagePolicyException, TransferProcessException { - return this.negotiate(providerConnectorUrl, catalogItem, - new EndpointDataReferenceStatus(null, EndpointDataReferenceStatus.TokenStatus.REQUIRED_NEW)); - } - public NegotiationResponse negotiate(final String providerConnectorUrl, final CatalogItem catalogItem, final EndpointDataReferenceStatus endpointDataReferenceStatus) throws ContractNegotiationException, UsagePolicyException, TransferProcessException { @@ -130,7 +124,7 @@ private CompletableFuture startNewNegotiation(final String if (!policyCheckerService.isValid(catalogItem.getPolicy())) { log.info("Policy was not allowed, canceling negotiation."); - throw new UsagePolicyException(catalogItem.getItemId()); + throw new UsagePolicyException(catalogItem.getItemId(), catalogItem.getPolicy()); } final NegotiationRequest negotiationRequest = createNegotiationRequestFromCatalogItem(providerConnectorUrl, diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcDataPlaneClient.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcDataPlaneClient.java index 545d80dddd..2bd8848407 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcDataPlaneClient.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcDataPlaneClient.java @@ -57,12 +57,24 @@ public EdcDataPlaneClient(@Qualifier("edcClientRestTemplate") final RestTemplate } public String getData(final EndpointDataReference dataReference, final String submodelDataplaneUrl) { - final String response = edcRestTemplate.exchange(submodelDataplaneUrl, HttpMethod.GET, new HttpEntity<>(null, headers(dataReference)), String.class).getBody(); log.info("Extracting raw embeddedData from EDC data plane response"); - return extractData(response); + return extractEmbeddedData(response); + } + + public EdcNotificationResponse sendData(final EndpointDataReference dataReference, + final EdcNotification notification) { + final String url = dataReference.getEndpoint(); + final HttpHeaders headers = headers(dataReference); + headers.setContentType(MediaType.APPLICATION_JSON); + + final ResponseEntity response = edcRestTemplate.exchange(url, HttpMethod.POST, + new HttpEntity<>(StringMapper.mapToString(notification), headers), String.class); + log.info("Call to {} returned with status code {}", url, response.getStatusCode()); + + return () -> response.getStatusCode().is2xxSuccessful(); } private HttpHeaders headers(final EndpointDataReference dataReference) { @@ -75,7 +87,7 @@ private HttpHeaders headers(final EndpointDataReference dataReference) { return headers; } - private String extractData(final String response) { + private String extractEmbeddedData(final String response) { String modifiedResponse = response; Matcher dataMatcher = RESPONSE_PATTERN.matcher(modifiedResponse); while (dataMatcher.matches()) { @@ -85,17 +97,4 @@ private String extractData(final String response) { } return modifiedResponse; } - - public EdcNotificationResponse sendData(final EndpointDataReference dataReference, - final EdcNotification notification) { - final String url = dataReference.getEndpoint(); - final HttpHeaders headers = headers(dataReference); - headers.setContentType(MediaType.APPLICATION_JSON); - - final ResponseEntity response = edcRestTemplate.exchange(url, HttpMethod.POST, - new HttpEntity<>(StringMapper.mapToString(notification), headers), String.class); - log.info("Call to {} returned with status code {}", url, response.getStatusCode()); - - return () -> response.getStatusCode().is2xxSuccessful(); - } } diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClient.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClient.java index a053e05da5..ccc37dd532 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClient.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClient.java @@ -28,6 +28,7 @@ import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.irs.edc.client.cache.endpointdatareference.EndpointDataReferenceStatus; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotification; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotificationResponse; import org.eclipse.tractusx.irs.edc.client.model.notification.NotificationContent; @@ -38,7 +39,7 @@ @SuppressWarnings("PMD.ExcessiveImports") public interface EdcSubmodelClient { - CompletableFuture getSubmodelRawPayload(String connectorEndpoint, String submodelDataplaneUrl, + CompletableFuture getSubmodelPayload(String connectorEndpoint, String submodelDataplaneUrl, String assetId) throws EdcClientException; CompletableFuture sendNotification(String submodelEndpointAddress, String assetId, diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientImpl.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientImpl.java index 72252ebc00..3cee65c88a 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientImpl.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientImpl.java @@ -36,6 +36,7 @@ import io.github.resilience4j.retry.RetryRegistry; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.UrlValidator; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.irs.edc.client.cache.endpointdatareference.EndpointDataReferenceCacheService; @@ -44,10 +45,12 @@ import org.eclipse.tractusx.irs.edc.client.model.CatalogItem; import org.eclipse.tractusx.irs.edc.client.model.EDRAuthCode; import org.eclipse.tractusx.irs.edc.client.model.NegotiationResponse; +import org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotification; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotificationResponse; import org.eclipse.tractusx.irs.edc.client.model.notification.NotificationContent; import org.eclipse.tractusx.irs.edc.client.util.Masker; +import org.jetbrains.annotations.Nullable; import org.springframework.util.StopWatch; /** @@ -86,19 +89,24 @@ private CompletableFuture sendNotificationAsync(final S .schedule(); } - private Optional retrieveSubmodelData(final String submodelDataplaneUrl, final StopWatch stopWatch, + private Optional retrieveSubmodelData(final String submodelDataplaneUrl, final StopWatch stopWatch, final EndpointDataReference endpointDataReference) { if (endpointDataReference != null) { log.info("Retrieving data from EDC data plane for dataReference with id {}", endpointDataReference.getId()); - final String data = edcDataPlaneClient.getData(endpointDataReference, submodelDataplaneUrl); + final String payload = edcDataPlaneClient.getData(endpointDataReference, submodelDataplaneUrl); stopWatchOnEdcTask(stopWatch); - return Optional.of(data); + return Optional.of(new SubmodelDescriptor(getContractAgreementId(endpointDataReference.getAuthCode()), payload)); } return Optional.empty(); } + @Nullable + private String getContractAgreementId(final String authCode) { + return StringUtils.isNotBlank(authCode) ? EDRAuthCode.fromAuthCodeToken(authCode).getCid() : null; + } + private Optional retrieveEndpointReference(final String storageId, final StopWatch stopWatch) { final Optional dataReference = retrieveEndpointDataReferenceByContractAgreementId( @@ -129,7 +137,7 @@ private Optional sendSubmodelNotification(final String } @Override - public CompletableFuture getSubmodelRawPayload(final String connectorEndpoint, + public CompletableFuture getSubmodelPayload(final String connectorEndpoint, final String submodelDataplaneUrl, final String assetId) throws EdcClientException { return execute(connectorEndpoint, () -> { log.info("Requesting raw SubmodelPayload for endpoint '{}'.", connectorEndpoint); @@ -138,7 +146,7 @@ public CompletableFuture getSubmodelRawPayload(final String connectorEnd final EndpointDataReference endpointDataReference = getEndpointDataReference(connectorEndpoint, assetId); - return pollingService.createJob() + return pollingService.createJob() .action(() -> retrieveSubmodelData(submodelDataplaneUrl, stopWatch, endpointDataReference)) .timeToLive(config.getSubmodel().getRequestTtl()) @@ -150,7 +158,7 @@ public CompletableFuture getSubmodelRawPayload(final String connectorEnd private EndpointDataReference getEndpointDataReference(final String connectorEndpoint, final String assetId) throws EdcClientException { - log.info("Retrieving endpoint data reference from cache for assed id: {}", assetId); + log.info("Retrieving endpoint data reference from cache for asset id: {}", assetId); final EndpointDataReferenceStatus cachedEndpointDataReference = endpointDataReferenceCacheService.getEndpointDataReference( assetId); EndpointDataReference endpointDataReference; diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientLocalStub.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientLocalStub.java index f0a4a2e826..7e0e37275a 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientLocalStub.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientLocalStub.java @@ -24,6 +24,7 @@ package org.eclipse.tractusx.irs.edc.client; import java.util.Map; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; @@ -31,6 +32,7 @@ import org.eclipse.tractusx.irs.data.StringMapper; import org.eclipse.tractusx.irs.edc.client.cache.endpointdatareference.EndpointDataReferenceStatus; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotification; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotificationResponse; import org.eclipse.tractusx.irs.edc.client.model.notification.NotificationContent; @@ -48,13 +50,14 @@ public EdcSubmodelClientLocalStub(final CxTestDataContainer cxTestDataContainer) } @Override - public CompletableFuture getSubmodelRawPayload(final String connectorEndpoint, + public CompletableFuture getSubmodelPayload(final String connectorEndpoint, final String submodelDataplaneUrl, final String assetId) throws EdcClientException { if ("urn:uuid:c35ee875-5443-4a2d-bc14-fdacd64b9446".equals(assetId)) { throw new EdcClientException("Dummy Exception"); } final Map submodel = testdataCreator.createSubmodelForId(assetId + "_" + submodelDataplaneUrl); - return CompletableFuture.completedFuture(StringMapper.mapToString(submodel)); + return CompletableFuture.completedFuture(new SubmodelDescriptor(UUID.randomUUID().toString(), + StringMapper.mapToString(submodel))); } @Override diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelFacade.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelFacade.java index 4e1646e8a9..0b5e0558f5 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelFacade.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelFacade.java @@ -29,6 +29,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotification; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotificationResponse; import org.eclipse.tractusx.irs.edc.client.model.notification.NotificationContent; @@ -44,10 +45,10 @@ public class EdcSubmodelFacade { private final EdcSubmodelClient client; @SuppressWarnings("PMD.PreserveStackTrace") - public String getSubmodelRawPayload(final String connectorEndpoint, final String submodelDataplaneUrl, + public SubmodelDescriptor getSubmodelPayload(final String connectorEndpoint, final String submodelDataplaneUrl, final String assetId) throws EdcClientException { try { - return client.getSubmodelRawPayload(connectorEndpoint, submodelDataplaneUrl, assetId).get(); + return client.getSubmodelPayload(connectorEndpoint, submodelDataplaneUrl, assetId).get(); } catch (InterruptedException e) { log.debug("InterruptedException occurred.", e); Thread.currentThread().interrupt(); diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/exceptions/UsagePolicyException.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/exceptions/UsagePolicyException.java index c3b704a890..dcf419790b 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/exceptions/UsagePolicyException.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/exceptions/UsagePolicyException.java @@ -23,12 +23,20 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.edc.client.exceptions; +import lombok.Getter; +import org.eclipse.edc.policy.model.Policy; + /** * Usage Policy Exception errors in the contract negotiation. */ +@Getter public class UsagePolicyException extends EdcClientException { - public UsagePolicyException(final String itemId) { + + private final transient Policy policy; + + public UsagePolicyException(final String itemId, final Policy policy) { super("Consumption of asset '" + itemId + "' is not permitted as the required catalog offer policies do not comply with defined IRS policies."); + this.policy = policy; } } diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Endpoint.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/model/SubmodelDescriptor.java similarity index 68% rename from irs-models/src/main/java/org/eclipse/tractusx/irs/component/Endpoint.java rename to irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/model/SubmodelDescriptor.java index 0a6f490d6e..7045bb2bad 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Endpoint.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/model/SubmodelDescriptor.java @@ -21,27 +21,22 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.irs.component; +package org.eclipse.tractusx.irs.edc.client.model; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; +import lombok.RequiredArgsConstructor; import lombok.Value; import lombok.extern.jackson.Jacksonized; /** - * Communication endpoint + * SubmodelDescriptor */ -@Schema(description = "Communication endpoint.") @Value -@Builder(toBuilder = true) +@Builder @Jacksonized -public class Endpoint { - - @Schema(description = "Communication interface type.", example = "HTTP", implementation = String.class, - defaultValue = "HTTP") - private String interfaceType; - - @Schema(description = "Information to the interface used.", implementation = ProtocolInformation.class) - private ProtocolInformation protocolInformation; +@RequiredArgsConstructor +public class SubmodelDescriptor { + private final String cid; + private final String payload; } diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Constraint.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Constraint.java index aa29305605..e9ab239c51 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Constraint.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Constraint.java @@ -23,9 +23,8 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.edc.client.policy; -import java.util.List; - -import io.swagger.v3.oas.annotations.media.ArraySchema; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -40,10 +39,13 @@ public class Constraint { @Schema(implementation = String.class, example = "string") + @JsonAlias({"odrl:leftOperand"}) private String leftOperand; - @Schema(implementation = OperatorType.class, example = "eq") - private OperatorType operator; - @ArraySchema(arraySchema = @Schema(example = "[\"string\"]", implementation = String.class)) - private List rightOperand; + @JsonAlias("odrl:operator") + @Schema + private Operator operator; + @Schema(implementation = String.class, example = "string") + @JsonProperty("odrl:rightOperand") + private String rightOperand; } diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/ConstraintCheckerService.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/ConstraintCheckerService.java index d90234fca5..ad5335b206 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/ConstraintCheckerService.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/ConstraintCheckerService.java @@ -23,7 +23,6 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.edc.client.policy; -import java.util.Collection; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -45,8 +44,7 @@ public class ConstraintCheckerService { public boolean hasAllConstraint(final Policy acceptedPolicy, final List constraints) { final List acceptedConstraintsList = acceptedPolicy.getPermissions() .stream() - .map(Permission::getConstraints) - .flatMap(Collection::stream) + .map(Permission::getConstraint) .toList(); return constraints.stream().allMatch(constraint -> isValidOnList(constraint, acceptedConstraintsList)); @@ -90,8 +88,8 @@ private boolean isSameAs(final AtomicConstraint atomicConstraint, .atomicConstraint(atomicConstraint) .leftExpressionValue(acceptedConstraint.getLeftOperand()) .rightExpressionValue( - acceptedConstraint.getRightOperand().stream().findFirst().orElse("")) - .expectedOperator(Operator.valueOf(acceptedConstraint.getOperator().name())) + acceptedConstraint.getRightOperand()) + .expectedOperator(Operator.valueOf(acceptedConstraint.getOperator().getOperatorType().name())) .build() .isValid(); } diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Constraints.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Constraints.java index 2963fa9878..fd00d20474 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Constraints.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Constraints.java @@ -25,6 +25,7 @@ import java.util.List; +import com.fasterxml.jackson.annotation.JsonAlias; import io.swagger.v3.oas.annotations.media.ArraySchema; import lombok.AllArgsConstructor; import lombok.Builder; @@ -42,8 +43,10 @@ public class Constraints { @ArraySchema + @JsonAlias({"odrl:and"}) private List and; @ArraySchema + @JsonAlias({"odrl:or"}) private List or; } diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/SemanticId.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Operator.java similarity index 63% rename from irs-models/src/main/java/org/eclipse/tractusx/irs/component/SemanticId.java rename to irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Operator.java index 76375a2e0b..1bbc693dd6 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/SemanticId.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Operator.java @@ -21,37 +21,24 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.irs.component; +package org.eclipse.tractusx.irs.edc.client.policy; -import java.util.List; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Singular; -import lombok.Value; +import lombok.Getter; +import lombok.NoArgsConstructor; /** - * SemanticId + * Wrapper for OperatorType */ -@Value -@Builder(toBuilder = true) -@Schema(description = "") +@Getter @AllArgsConstructor -@JsonDeserialize(builder = SemanticId.SemanticIdBuilder.class) -public class SemanticId { - - @Schema() - @Singular - private List values; +@NoArgsConstructor +@Schema +public class Operator { - /** - * User to build SemanticId - */ - @Schema(description = "User to build async fetched items") - @JsonPOJOBuilder(withPrefix = "") - public static class SemanticIdBuilder { - } + @JsonProperty("@id") + @Schema(implementation = OperatorType.class, example = "odrl:eq") + private OperatorType operatorType; } diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/OperatorType.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/OperatorType.java index 9adde7bca9..0c8ff4165b 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/OperatorType.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/OperatorType.java @@ -55,6 +55,8 @@ public enum OperatorType { private final String code; private final String label; + private static final int ODRL_PREFIX_INDEX = 5; + OperatorType(final String code, final String label) { this.code = code; this.label = label; @@ -62,10 +64,17 @@ public enum OperatorType { @JsonCreator public static OperatorType fromValue(final String value) { + String operator; + if (value.startsWith("odrl:")) { + operator = value.substring(ODRL_PREFIX_INDEX); + } else { + operator = value; + } + return Stream.of(OperatorType.values()) - .filter(operatorType -> operatorType.code.equals(value)) + .filter(operatorType -> operatorType.code.equals(operator)) .findFirst() - .orElseThrow(() -> new NoSuchElementException("Unsupported OperatorType: " + value)); + .orElseThrow(() -> new NoSuchElementException("Unsupported OperatorType: " + operator)); } /** diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Permission.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Permission.java index 97eabdc6d8..a447e02388 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Permission.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Permission.java @@ -23,9 +23,8 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.edc.client.policy; -import java.util.List; - -import io.swagger.v3.oas.annotations.media.ArraySchema; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; @@ -39,11 +38,14 @@ @AllArgsConstructor @NoArgsConstructor @Builder +@JsonIgnoreProperties(ignoreUnknown = true) public class Permission { @Schema(implementation = PolicyType.class, example = "USE") + @JsonAlias({"odrl:action"}) private PolicyType action; - @ArraySchema - private List constraints; + @Schema + @JsonAlias({"odrl:constraint"}) + private Constraints constraint; } diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Policy.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Policy.java index fb34597ba8..de81a2ae83 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Policy.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/policy/Policy.java @@ -26,18 +26,21 @@ import java.time.OffsetDateTime; import java.util.List; +import com.fasterxml.jackson.annotation.JsonAlias; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.extern.jackson.Jacksonized; /** * A stored policy object. */ @Getter +@Setter @AllArgsConstructor @NoArgsConstructor @Builder @@ -52,6 +55,7 @@ public class Policy { @Schema(implementation = OffsetDateTime.class) private OffsetDateTime validUntil; @ArraySchema(schema = @Schema) + @JsonAlias({ "odrl:permission" }) private List permissions; public Policy update(final OffsetDateTime validUntil) { diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/transformer/EdcTransformer.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/transformer/EdcTransformer.java index 8e25830fe0..da9b59f4f8 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/transformer/EdcTransformer.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/transformer/EdcTransformer.java @@ -36,7 +36,6 @@ import org.eclipse.edc.catalog.spi.Catalog; import org.eclipse.edc.catalog.spi.CatalogRequest; import org.eclipse.edc.core.transform.TransformerContextImpl; -import org.eclipse.edc.core.transform.TypeTransformerRegistryImpl; import org.eclipse.edc.core.transform.transformer.from.JsonObjectFromAssetTransformer; import org.eclipse.edc.core.transform.transformer.from.JsonObjectFromCatalogTransformer; import org.eclipse.edc.core.transform.transformer.from.JsonObjectFromCriterionTransformer; @@ -67,6 +66,7 @@ import org.eclipse.tractusx.irs.edc.client.model.ContractOfferDescription; import org.eclipse.tractusx.irs.edc.client.model.NegotiationRequest; import org.eclipse.tractusx.irs.edc.client.model.TransferProcessRequest; +import org.eclipse.tractusx.irs.edc.client.policy.Policy; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -81,11 +81,12 @@ public class EdcTransformer { private final JsonObjectFromTransferProcessRequestTransformer jsonObjectFromTransferProcessRequestTransformer; private final JsonObjectFromContractOfferDescriptionTransformer jsonObjectFromContractOfferDescriptionTransformer; private final JsonObjectFromCatalogRequestTransformer jsonObjectFromCatalogRequestTransformer; + private final org.eclipse.tractusx.irs.edc.client.transformer.JsonObjectToPolicyTransformer jsonObjectToPolicyTransformer; private final TitaniumJsonLd titaniumJsonLd; private final TransformerContextImpl transformerContext; public EdcTransformer(@Qualifier("jsonLdObjectMapper") final ObjectMapper objectMapper, - final TitaniumJsonLd titaniumJsonLd) { + final TitaniumJsonLd titaniumJsonLd, final TypeTransformerRegistry typeTransformerRegistry) { this.titaniumJsonLd = titaniumJsonLd; final JsonBuilderFactory jsonBuilderFactory = Json.createBuilderFactory(Map.of()); @@ -97,8 +98,8 @@ public EdcTransformer(@Qualifier("jsonLdObjectMapper") final ObjectMapper object jsonObjectFromContractOfferDescriptionTransformer = new JsonObjectFromContractOfferDescriptionTransformer( jsonBuilderFactory); jsonObjectFromCatalogRequestTransformer = new JsonObjectFromCatalogRequestTransformer(jsonBuilderFactory); + jsonObjectToPolicyTransformer = new org.eclipse.tractusx.irs.edc.client.transformer.JsonObjectToPolicyTransformer(objectMapper); - final TypeTransformerRegistry typeTransformerRegistry = new TypeTransformerRegistryImpl(); transformerContext = new TransformerContextImpl(typeTransformerRegistry); // JSON to Object @@ -167,4 +168,8 @@ public JsonObject transformCatalogRequestToJson(final CatalogRequest catalogRequ transformerContext); return titaniumJsonLd.compact(transform).asOptional().orElseThrow(); } + + public Policy transformToPolicy(final JsonObject body) { + return jsonObjectToPolicyTransformer.transform(body, transformerContext); + } } diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/transformer/JsonObjectToPolicyTransformer.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/transformer/JsonObjectToPolicyTransformer.java new file mode 100644 index 0000000000..7e14e41e82 --- /dev/null +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/transformer/JsonObjectToPolicyTransformer.java @@ -0,0 +1,75 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2024: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2024 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.irs.edc.client.transformer; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.JsonObject; +import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.eclipse.tractusx.irs.data.JsonParseException; +import org.eclipse.tractusx.irs.edc.client.policy.Policy; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Transformer to convert JSON-LD to Policy. + */ +public class JsonObjectToPolicyTransformer extends AbstractJsonLdTransformer { + + private final ObjectMapper objectMapper; + + protected JsonObjectToPolicyTransformer(final ObjectMapper objectMapper) { + super(JsonObject.class, Policy.class); + this.objectMapper = objectMapper; + } + + @Override + public @Nullable Policy transform(@NotNull final JsonObject jsonObject, + @NotNull final TransformerContext transformerContext) { + final Policy.PolicyBuilder builder = Policy.builder(); + builder.policyId(getId(jsonObject)); + + this.visitProperties(jsonObject, key -> v -> { + try { + final Object result = objectMapper.readerFor(Policy.class).readValue(v.asJsonObject().toString()); + builder.permissions(((Policy) result).getPermissions()); + } catch (JsonProcessingException e) { + throw new JsonParseException(e); + } + }); + + return builder.build(); + } + + private String getId(final JsonObject jsonObject) { + try { + final JsonNode jsonNode = objectMapper.readTree(jsonObject.toString()); + return jsonNode.path("@id").asText(); + } catch (JsonProcessingException e) { + throw new JsonParseException(e); + } + } +} diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/CxTestDataAnalyzerTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/CxTestDataAnalyzerTest.java index 99d5d69c5a..c6ec277d58 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/CxTestDataAnalyzerTest.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/CxTestDataAnalyzerTest.java @@ -348,7 +348,7 @@ private void checkAndIncrementCounter(final boolean shouldCountSubmodel, private void checkAndAddSubmodel(final boolean shouldCountSubmodel, final Optional> payload, final List submodels, final String aspectType) { if (shouldCountSubmodel && payload.isPresent()) { - submodels.add(Submodel.from(UUID.randomUUID().toString(), aspectType, payload.get())); + submodels.add(Submodel.from(UUID.randomUUID().toString(), aspectType, UUID.randomUUID().toString(), payload.get())); } } diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientLocalStubTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientLocalStubTest.java index af0c4ebc3b..9be5557c9c 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientLocalStubTest.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientLocalStubTest.java @@ -47,6 +47,6 @@ void shouldThrowExceptionFor() { String assetId = "urn:uuid:c35ee875-5443-4a2d-bc14-fdacd64b9446"; // when - assertThrows(EdcClientException.class, () -> edcSubmodelClientLocalStub.getSubmodelRawPayload("", "", assetId)); + assertThrows(EdcClientException.class, () -> edcSubmodelClientLocalStub.getSubmodelPayload("", "", assetId)); } } \ No newline at end of file diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientTest.java index 3f0a921243..5e2b06ea4b 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientTest.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelClientTest.java @@ -72,6 +72,7 @@ import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotification; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotificationResponse; import org.eclipse.tractusx.irs.edc.client.model.notification.NotificationContent; +import org.eclipse.tractusx.irs.edc.client.testutil.TestMother; import org.eclipse.tractusx.irs.testing.containers.LocalTestDataConfigurationAware; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; @@ -137,7 +138,7 @@ void shouldRetrieveValidRelationship() throws Exception { when(contractNegotiationService.negotiate(any(), any(), eq(new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)))).thenReturn( NegotiationResponse.builder().contractAgreementId("agreementId").build()); - final EndpointDataReference ref = mock(EndpointDataReference.class); + final EndpointDataReference ref = TestMother.endpointDataReference("agreementId"); endpointDataReferenceStorage.put("agreementId", ref); final String singleLevelBomAsBuiltJson = readSingleLevelBomAsBuiltData(); when(edcDataPlaneClient.getData(eq(ref), any())).thenReturn(singleLevelBomAsBuiltJson); @@ -145,8 +146,8 @@ void shouldRetrieveValidRelationship() throws Exception { new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); // act - final var result = testee.getSubmodelRawPayload(ENDPOINT_ADDRESS, "suffix", "assetId"); - final String resultingRelationships = result.get(5, TimeUnit.SECONDS); + final var result = testee.getSubmodelPayload(ENDPOINT_ADDRESS, "suffix", "assetId"); + final String resultingRelationships = result.get(5, TimeUnit.SECONDS).getPayload(); // assert assertThat(resultingRelationships).isNotNull().isEqualTo(singleLevelBomAsBuiltJson); @@ -194,8 +195,8 @@ void shouldReturnRelationshipsWhenRequestingWithCatenaXIdAndSingleLevelBomAsBuil when(endpointDataReferenceCacheService.getEndpointDataReference(any())).thenReturn( new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); - final String submodelResponse = testee.getSubmodelRawPayload("http://localhost/", "/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS); + final String submodelResponse = testee.getSubmodelPayload("http://localhost/", "/submodel", ASSET_ID) + .get(5, TimeUnit.SECONDS).getPayload(); assertThat(submodelResponse).contains(existingCatenaXId); } @@ -209,8 +210,8 @@ void shouldReturnRelationshipsWhenRequestingWithCatenaXIdAndSingleLevelBomAsPlan when(endpointDataReferenceCacheService.getEndpointDataReference(any())).thenReturn( new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); - final String submodelResponse = testee.getSubmodelRawPayload("http://localhost/", "/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS); + final String submodelResponse = testee.getSubmodelPayload("http://localhost/", "/submodel", ASSET_ID) + .get(5, TimeUnit.SECONDS).getPayload(); assertThat(submodelResponse).contains("urn:uuid:e5c96ab5-896a-482c-8761-efd74777ca97"); } @@ -224,8 +225,8 @@ void shouldReturnRelationshipsWhenRequestingWithCatenaXIdAndSingleLevelBomAsSpec when(endpointDataReferenceCacheService.getEndpointDataReference(any())).thenReturn( new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); - final String submodelResponse = testee.getSubmodelRawPayload("http://localhost/", "/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS); + final String submodelResponse = testee.getSubmodelPayload("http://localhost/", "/submodel", ASSET_ID) + .get(5, TimeUnit.SECONDS).getPayload(); assertThat(submodelResponse).contains("urn:uuid:2afbac90-a662-4f16-9058-4f030e692631"); } @@ -239,8 +240,8 @@ void shouldReturnEmptyRelationshipsWhenRequestingWithCatenaXIdAndSingleLevelUsag when(endpointDataReferenceCacheService.getEndpointDataReference(any())).thenReturn( new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); - final String submodelResponse = testee.getSubmodelRawPayload("http://localhost/", "/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS); + final String submodelResponse = testee.getSubmodelPayload("http://localhost/", "/submodel", ASSET_ID) + .get(5, TimeUnit.SECONDS).getPayload(); assertThat(submodelResponse).isNotEmpty(); } @@ -255,8 +256,8 @@ void shouldReturnEmptyRelationshipsWhenRequestingWithNotExistingCatenaXIdAndSing when(endpointDataReferenceCacheService.getEndpointDataReference(ASSET_ID)).thenReturn( new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); - final String submodelResponse = testee.getSubmodelRawPayload("http://localhost/", "/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS); + final String submodelResponse = testee.getSubmodelPayload("http://localhost/", "/submodel", ASSET_ID) + .get(5, TimeUnit.SECONDS).getPayload(); assertThat(submodelResponse).isEqualTo("{}"); } @@ -270,8 +271,9 @@ void shouldReturnRawSerialPartWhenExisting() throws Exception { when(endpointDataReferenceCacheService.getEndpointDataReference(ASSET_ID)).thenReturn( new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); - final String submodelResponse = testee.getSubmodelRawPayload("https://connector.endpoint.com", - "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", ASSET_ID).get(5, TimeUnit.SECONDS); + final String submodelResponse = testee.getSubmodelPayload("https://connector.endpoint.com", + "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", ASSET_ID) + .get(5, TimeUnit.SECONDS).getPayload(); assertThat(submodelResponse).startsWith( "{\"localIdentifiers\":[{\"value\":\"BPNL00000003AVTH\",\"key\":\"manufacturerId\"}"); @@ -287,8 +289,9 @@ void shouldUseDecodedTargetId() throws Exception { when(endpointDataReferenceCacheService.getEndpointDataReference(ASSET_ID)).thenReturn( new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); - final String submodelResponse = testee.getSubmodelRawPayload("https://connector.endpoint.com", - "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", ASSET_ID).get(5, TimeUnit.SECONDS); + final String submodelResponse = testee.getSubmodelPayload("https://connector.endpoint.com", + "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", ASSET_ID) + .get(5, TimeUnit.SECONDS).getPayload(); assertThat(submodelResponse).startsWith( "{\"localIdentifiers\":[{\"value\":\"BPNL00000003AVTH\",\"key\":\"manufacturerId\"}"); @@ -303,8 +306,8 @@ void shouldReturnSameRelationshipsForDifferentDirections() throws Exception { prepareTestdata(parentCatenaXId, "_singleLevelBomAsBuilt"); when(endpointDataReferenceCacheService.getEndpointDataReference(ASSET_ID)).thenReturn( new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); - final String relationshipsJson = testee.getSubmodelRawPayload("http://localhost/", "_singleLevelBomAsBuilt", - ASSET_ID).get(5, TimeUnit.SECONDS); + final String relationshipsJson = testee.getSubmodelPayload("http://localhost/", "_singleLevelBomAsBuilt", + ASSET_ID).get(5, TimeUnit.SECONDS).getPayload(); final var relationships = StringMapper.mapFromString(relationshipsJson, RelationshipAspect.from(asBuilt, Direction.DOWNWARD).getSubmodelClazz()).asRelationships(); @@ -316,8 +319,8 @@ void shouldReturnSameRelationshipsForDifferentDirections() throws Exception { .orElseThrow(); prepareTestdata(childCatenaXId.getGlobalAssetId(), "_singleLevelUsageAsBuilt"); - final String singleLevelUsageRelationshipsJson = testee.getSubmodelRawPayload("http://localhost/", - "_singleLevelUsageAsBuilt", ASSET_ID).get(5, TimeUnit.SECONDS); + final String singleLevelUsageRelationshipsJson = testee.getSubmodelPayload("http://localhost/", + "_singleLevelUsageAsBuilt", ASSET_ID).get(5, TimeUnit.SECONDS).getPayload(); final var singleLevelUsageRelationships = StringMapper.mapFromString(singleLevelUsageRelationshipsJson, RelationshipAspect.from(asBuilt, Direction.UPWARD).getSubmodelClazz()).asRelationships(); @@ -378,17 +381,16 @@ void shouldUseCachedEndpointReferenceValueWhenTokenIsValid() throws EdcClientException, ExecutionException, InterruptedException { // given when(endpointDataReferenceCacheService.getEndpointDataReference(any())).thenReturn( - new EndpointDataReferenceStatus( - EndpointDataReference.Builder.newInstance().endpoint("").authKey("").authCode("").build(), + new EndpointDataReferenceStatus(TestMother.endpointDataReference("assetId"), TokenStatus.VALID)); final String value = "result"; when(edcDataPlaneClient.getData(any(), any())).thenReturn(value); // when - final var resultFuture = testee.getSubmodelRawPayload(ENDPOINT_ADDRESS, "suffix", "assetId"); + final var resultFuture = testee.getSubmodelPayload(ENDPOINT_ADDRESS, "suffix", "assetId"); // then - final String result = resultFuture.get(); + final String result = resultFuture.get().getPayload(); verify(contractNegotiationService, never()).negotiate(any(), any(), any()); assertThat(result).isEqualTo(value); } @@ -407,7 +409,7 @@ void shouldCreateCacheRecordWhenTokenIsNotValid() throws EdcClientException { new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); // when - testee.getSubmodelRawPayload(ENDPOINT_ADDRESS, "suffix", "assetId"); + testee.getSubmodelPayload(ENDPOINT_ADDRESS, "suffix", "assetId"); // then final Optional referenceFromStorage = endpointDataReferenceStorage.get("assetId"); @@ -420,7 +422,7 @@ private void prepareTestdata(final String catenaXId, final String submodelDataSu when(contractNegotiationService.negotiate(any(), any(), eq(new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)))).thenReturn( NegotiationResponse.builder().contractAgreementId("agreementId").build()); - final EndpointDataReference ref = mock(EndpointDataReference.class); + final EndpointDataReference ref = TestMother.endpointDataReference("agreementId"); endpointDataReferenceStorage.put("agreementId", ref); final SubmodelTestdataCreator submodelTestdataCreator = new SubmodelTestdataCreator( localTestDataConfiguration.cxTestDataContainer()); diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelFacadeTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelFacadeTest.java index a0430a84d9..5b384f1ccc 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelFacadeTest.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/EdcSubmodelFacadeTest.java @@ -35,7 +35,10 @@ import org.assertj.core.api.ThrowableAssert; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotificationResponse; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -55,134 +58,151 @@ class EdcSubmodelFacadeTest { @Mock private EdcSubmodelClient client; + @Nested + @DisplayName("getSubmodelRawPayload") + class GetSubmodelRawPayloadTests { + + @Test + void shouldThrowExecutionExceptionForSubmodel() throws EdcClientException { + // arrange + final ExecutionException e = new ExecutionException(new EdcClientException("test")); + final CompletableFuture future = CompletableFuture.failedFuture(e); + when(client.getSubmodelPayload(any(), any(), any())).thenReturn(future); + + // act + ThrowableAssert.ThrowingCallable action = () -> testee.getSubmodelPayload(CONNECTOR_ENDPOINT, + SUBMODEL_SUFIX, ASSET_ID); + + // assert + assertThatThrownBy(action).isInstanceOf(EdcClientException.class); + } + + @Test + void shouldThrowEdcClientExceptionForSubmodel() throws EdcClientException { + // arrange + final EdcClientException e = new EdcClientException("test"); + when(client.getSubmodelPayload(any(), any(), any())).thenThrow(e); + + // act + ThrowableAssert.ThrowingCallable action = () -> testee.getSubmodelPayload(CONNECTOR_ENDPOINT, + SUBMODEL_SUFIX, ASSET_ID); + + // assert + assertThatThrownBy(action).isInstanceOf(EdcClientException.class); + } + + @Test + void shouldRestoreInterruptOnInterruptExceptionForSubmodel() + throws EdcClientException, ExecutionException, InterruptedException { + // arrange + final CompletableFuture future = mock(CompletableFuture.class); + final InterruptedException e = new InterruptedException(); + when(future.get()).thenThrow(e); + when(client.getSubmodelPayload(any(), any(), any())).thenReturn(future); + + // act + testee.getSubmodelPayload(CONNECTOR_ENDPOINT, SUBMODEL_SUFIX, ASSET_ID); + + // assert + assertThat(Thread.currentThread().isInterrupted()).isTrue(); + } - @Test - void shouldThrowExecutionExceptionForSubmodel() throws EdcClientException { - // arrange - final ExecutionException e = new ExecutionException(new EdcClientException("test")); - final CompletableFuture future = CompletableFuture.failedFuture(e); - when(client.getSubmodelRawPayload(any(), any(), any())).thenReturn(future); - - // act - ThrowableAssert.ThrowingCallable action = () -> testee.getSubmodelRawPayload(CONNECTOR_ENDPOINT, SUBMODEL_SUFIX, ASSET_ID); - - // assert - assertThatThrownBy(action).isInstanceOf(EdcClientException.class); - } - - @Test - void shouldThrowEdcClientExceptionForSubmodel() throws EdcClientException { - // arrange - final EdcClientException e = new EdcClientException("test"); - when(client.getSubmodelRawPayload(any(), any(), any())).thenThrow(e); - - // act - ThrowableAssert.ThrowingCallable action = () -> testee.getSubmodelRawPayload(CONNECTOR_ENDPOINT, SUBMODEL_SUFIX, ASSET_ID); - - // assert - assertThatThrownBy(action).isInstanceOf(EdcClientException.class); - } - - @Test - void shouldRestoreInterruptOnInterruptExceptionForSubmodel() - throws EdcClientException, ExecutionException, InterruptedException { - // arrange - final CompletableFuture future = mock(CompletableFuture.class); - final InterruptedException e = new InterruptedException(); - when(future.get()).thenThrow(e); - when(client.getSubmodelRawPayload(any(), any(), any())).thenReturn(future); - - // act - testee.getSubmodelRawPayload(CONNECTOR_ENDPOINT, SUBMODEL_SUFIX, ASSET_ID); - - // assert - assertThat(Thread.currentThread().isInterrupted()).isTrue(); - } - - @Test - void shouldRestoreInterruptOnInterruptExceptionForNotification() - throws EdcClientException, ExecutionException, InterruptedException { - // arrange - final CompletableFuture future = mock(CompletableFuture.class); - final InterruptedException e = new InterruptedException(); - when(future.get()).thenThrow(e); - when(client.sendNotification(any(), any(), any())).thenReturn(future); - - // act - testee.sendNotification("", "notify-request-asset", null); - - // assert - assertThat(Thread.currentThread().isInterrupted()).isTrue(); - } - - @Test - void shouldThrowExecutionExceptionForNotification() throws EdcClientException { - // arrange - final ExecutionException e = new ExecutionException(new EdcClientException("test")); - final CompletableFuture future = CompletableFuture.failedFuture(e); - when(client.sendNotification(any(), any(), any())).thenReturn(future); - - // act - ThrowableAssert.ThrowingCallable action = () -> testee.sendNotification("", "notify-request-asset", null); - - // assert - assertThatThrownBy(action).isInstanceOf(EdcClientException.class); } - @Test - void shouldThrowEdcClientExceptionForNotification() throws EdcClientException { - // arrange - final EdcClientException e = new EdcClientException("test"); - when(client.sendNotification(any(), any(), any())).thenThrow(e); - - // act - ThrowableAssert.ThrowingCallable action = () -> testee.sendNotification("", "notify-request-asset", null); - - // assert - assertThatThrownBy(action).isInstanceOf(EdcClientException.class); + @Nested + @DisplayName("sendNotification") + class SendNotificationTests { + + @Test + void shouldRestoreInterruptOnInterruptExceptionForNotification() + throws EdcClientException, ExecutionException, InterruptedException { + // arrange + final CompletableFuture future = mock(CompletableFuture.class); + final InterruptedException e = new InterruptedException(); + when(future.get()).thenThrow(e); + when(client.sendNotification(any(), any(), any())).thenReturn(future); + + // act + testee.sendNotification("", "notify-request-asset", null); + + // assert + assertThat(Thread.currentThread().isInterrupted()).isTrue(); + } + + @Test + void shouldThrowExecutionExceptionForNotification() throws EdcClientException { + // arrange + final ExecutionException e = new ExecutionException(new EdcClientException("test")); + final CompletableFuture future = CompletableFuture.failedFuture(e); + when(client.sendNotification(any(), any(), any())).thenReturn(future); + + // act + ThrowableAssert.ThrowingCallable action = () -> testee.sendNotification("", "notify-request-asset", null); + + // assert + assertThatThrownBy(action).isInstanceOf(EdcClientException.class); + } + + @Test + void shouldThrowEdcClientExceptionForNotification() throws EdcClientException { + // arrange + final EdcClientException e = new EdcClientException("test"); + when(client.sendNotification(any(), any(), any())).thenThrow(e); + + // act + ThrowableAssert.ThrowingCallable action = () -> testee.sendNotification("", "notify-request-asset", null); + + // assert + assertThatThrownBy(action).isInstanceOf(EdcClientException.class); + } } - @Test - void shouldThrowEdcClientExceptionForEndpointReference() throws EdcClientException { - // arrange - final EdcClientException e = new EdcClientException("test"); - when(client.getEndpointReferenceForAsset(any(), any(), any())).thenThrow(e); - - // act - ThrowableAssert.ThrowingCallable action = () -> testee.getEndpointReferenceForAsset("", "", ""); - - // assert - assertThatThrownBy(action).isInstanceOf(EdcClientException.class); - } - - @Test - void shouldThrowExecutionExceptionForEndpointReference() throws EdcClientException { - // arrange - final ExecutionException e = new ExecutionException(new EdcClientException("test")); - final CompletableFuture future = CompletableFuture.failedFuture(e); - when(client.getEndpointReferenceForAsset(any(), any(), any())).thenReturn(future); - - // act - ThrowableAssert.ThrowingCallable action = () -> testee.getEndpointReferenceForAsset("", "", ""); - - // assert - assertThatThrownBy(action).isInstanceOf(EdcClientException.class); - } - - @Test - void shouldRestoreInterruptOnInterruptExceptionForEndpointReference() - throws EdcClientException, ExecutionException, InterruptedException { - // arrange - final CompletableFuture future = mock(CompletableFuture.class); - final InterruptedException e = new InterruptedException(); - when(future.get()).thenThrow(e); - when(client.getEndpointReferenceForAsset(any(), any(), any())).thenReturn(future); - - // act - testee.getEndpointReferenceForAsset("", "", ""); - - // assert - assertThat(Thread.currentThread().isInterrupted()).isTrue(); + @Nested + @DisplayName("getEndpointReferenceForAsset") + class GetEndpointReferenceForAssetTests { + + @Test + void shouldThrowEdcClientExceptionForEndpointReference() throws EdcClientException { + // arrange + final EdcClientException e = new EdcClientException("test"); + when(client.getEndpointReferenceForAsset(any(), any(), any())).thenThrow(e); + + // act + ThrowableAssert.ThrowingCallable action = () -> testee.getEndpointReferenceForAsset("", "", ""); + + // assert + assertThatThrownBy(action).isInstanceOf(EdcClientException.class); + } + + @Test + void shouldThrowExecutionExceptionForEndpointReference() throws EdcClientException { + // arrange + final ExecutionException e = new ExecutionException(new EdcClientException("test")); + final CompletableFuture future = CompletableFuture.failedFuture(e); + when(client.getEndpointReferenceForAsset(any(), any(), any())).thenReturn(future); + + // act + ThrowableAssert.ThrowingCallable action = () -> testee.getEndpointReferenceForAsset("", "", ""); + + // assert + assertThatThrownBy(action).isInstanceOf(EdcClientException.class); + } + + @Test + void shouldRestoreInterruptOnInterruptExceptionForEndpointReference() + throws EdcClientException, ExecutionException, InterruptedException { + // arrange + final CompletableFuture future = mock(CompletableFuture.class); + final InterruptedException e = new InterruptedException(); + when(future.get()).thenThrow(e); + when(client.getEndpointReferenceForAsset(any(), any(), any())).thenReturn(future); + + // act + testee.getEndpointReferenceForAsset("", "", ""); + + // assert + assertThat(Thread.currentThread().isInterrupted()).isTrue(); + } } } \ No newline at end of file diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelFacadeWiremockTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelFacadeWiremockTest.java index 033fe200f5..f96987c366 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelFacadeWiremockTest.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelFacadeWiremockTest.java @@ -23,24 +23,25 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.edc.client; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.configureFor; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.givenThat; -import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.eclipse.tractusx.irs.edc.client.configuration.JsonLdConfiguration.NAMESPACE_EDC_CID; import static org.eclipse.tractusx.irs.edc.client.testutil.TestMother.createEdcTransformer; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.DATAPLANE_HOST; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_DATAPLANE_PUBLIC; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Duration; import java.time.OffsetDateTime; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -48,49 +49,53 @@ import java.util.concurrent.TimeUnit; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; import io.github.resilience4j.retry.RetryRegistry; import org.assertj.core.api.ThrowableAssert; import org.eclipse.edc.policy.model.PolicyRegistrationTypes; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.irs.data.StringMapper; import org.eclipse.tractusx.irs.edc.client.cache.endpointdatareference.EndpointDataReferenceCacheService; +import org.eclipse.tractusx.irs.edc.client.configuration.JsonLdConfiguration; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; import org.eclipse.tractusx.irs.edc.client.exceptions.UsagePolicyException; +import org.eclipse.tractusx.irs.edc.client.model.EDRAuthCode; import org.eclipse.tractusx.irs.edc.client.policy.AcceptedPoliciesProvider; import org.eclipse.tractusx.irs.edc.client.policy.AcceptedPolicy; import org.eclipse.tractusx.irs.edc.client.policy.Constraint; import org.eclipse.tractusx.irs.edc.client.policy.ConstraintCheckerService; import org.eclipse.tractusx.irs.edc.client.policy.Constraints; +import org.eclipse.tractusx.irs.edc.client.policy.Operator; import org.eclipse.tractusx.irs.edc.client.policy.OperatorType; import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.PolicyCheckerService; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; -import org.junit.jupiter.api.AfterEach; +import org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; +@WireMockTest class SubmodelFacadeWiremockTest { - private final static String connectorEndpoint = "https://connector.endpoint.com"; - private final static String submodelDataplanePath = "/api/public/shells/12345/submodels/5678/submodel"; - private final static String assetId = "12345"; + private static final String PROXY_SERVER_HOST = "127.0.0.1"; + private final static String CONNECTOR_ENDPOINT_URL = "https://connector.endpoint.com"; + private final static String SUBMODEL_DATAPLANE_PATH = "/api/public/shells/12345/submodels/5678/submodel"; + private final static String SUBMODEL_DATAPLANE_URL = "http://dataplane.test" + SUBMODEL_DATAPLANE_PATH; + private final static String ASSET_ID = "12345"; private final EdcConfiguration config = new EdcConfiguration(); private final EndpointDataReferenceStorage storage = new EndpointDataReferenceStorage(Duration.ofMinutes(1)); - private WireMockServer wireMockServer; private EdcSubmodelClient edcSubmodelClient; private AcceptedPoliciesProvider acceptedPoliciesProvider; @BeforeEach - void configureSystemUnderTest() { - this.wireMockServer = new WireMockServer(options().dynamicPort()); - this.wireMockServer.start(); - configureFor(this.wireMockServer.port()); + void configureSystemUnderTest(WireMockRuntimeInfo wireMockRuntimeInfo) { + final RestTemplate restTemplate = restTemplateProxy(PROXY_SERVER_HOST, wireMockRuntimeInfo.getHttpPort()); - config.getControlplane().getEndpoint().setData(buildApiMethodUrl()); + config.getControlplane().getEndpoint().setData("http://controlplane.test"); config.getControlplane().getEndpoint().setCatalog("/catalog/request"); config.getControlplane().getEndpoint().setContractNegotiation("/contractnegotiations"); config.getControlplane().getEndpoint().setTransferProcess("/transferprocesses"); @@ -99,7 +104,6 @@ void configureSystemUnderTest() { config.getControlplane().setProviderSuffix("/api/v1/dsp"); config.getSubmodel().setUrnPrefix("/urn"); - final RestTemplate restTemplate = new RestTemplateBuilder().build(); final List> messageConverters = restTemplate.getMessageConverters(); for (final HttpMessageConverter converter : messageConverters) { if (converter instanceof final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) { @@ -117,14 +121,15 @@ void configureSystemUnderTest() { final EdcDataPlaneClient dataPlaneClient = new EdcDataPlaneClient(restTemplate); final EDCCatalogFacade catalogFacade = new EDCCatalogFacade(controlPlaneClient, config); - final EndpointDataReferenceCacheService endpointDataReferenceCacheService = new EndpointDataReferenceCacheService(new EndpointDataReferenceStorage(Duration.ofMinutes(1))); + final EndpointDataReferenceCacheService endpointDataReferenceCacheService = new EndpointDataReferenceCacheService( + new EndpointDataReferenceStorage(Duration.ofMinutes(1))); acceptedPoliciesProvider = mock(AcceptedPoliciesProvider.class); when(acceptedPoliciesProvider.getAcceptedPolicies()).thenReturn(List.of(new AcceptedPolicy(policy("IRS Policy", - List.of(new Permission(PolicyType.USE, List.of(new Constraints( - List.of(new Constraint("Membership", OperatorType.EQ, List.of("active")), - new Constraint("FrameworkAgreement.traceability", OperatorType.EQ, List.of("active"))), - new ArrayList<>()))))), OffsetDateTime.now().plusYears(1)))); + List.of(new Permission(PolicyType.USE, new Constraints( + List.of(new Constraint("Membership", new Operator(OperatorType.EQ), "active"), + new Constraint("FrameworkAgreement.traceability", new Operator(OperatorType.EQ), "active")), + new ArrayList<>())))), OffsetDateTime.now().plusYears(1)))); final PolicyCheckerService policyCheckerService = new PolicyCheckerService(acceptedPoliciesProvider, new ConstraintCheckerService()); final ContractNegotiationService contractNegotiationService = new ContractNegotiationService(controlPlaneClient, @@ -135,97 +140,33 @@ void configureSystemUnderTest() { pollingService, retryRegistry, catalogFacade, endpointDataReferenceCacheService); } - @AfterEach - void tearDown() { - this.wireMockServer.stop(); - } - @Test void shouldReturnAssemblyPartRelationshipAsString() throws EdcClientException, ExecutionException, InterruptedException { // Arrange prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBodyFile( - "singleLevelBomAsBuilt.json"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn( + responseWithStatus(200).withBodyFile("singleLevelBomAsBuilt.json"))); // Act - final String submodel = edcSubmodelClient.getSubmodelRawPayload(connectorEndpoint, buildWiremockDataplaneUrl(), - assetId).get(); + final String submodel = edcSubmodelClient.getSubmodelPayload(CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, + ASSET_ID).get().getPayload(); // Assert assertThat(submodel).contains("\"catenaXId\": \"urn:uuid:fe99da3d-b0de-4e80-81da-882aebcca978\""); } - private void prepareNegotiation() { - final var contentType = "application/json;charset=UTF-8"; - final var pathCatalog = "/catalog/request"; - final var pathNegotiate = "/contractnegotiations"; - final var pathTransfer = "/transferprocesses"; - givenThat(post(urlPathEqualTo(pathCatalog)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", contentType) - .withBodyFile("edc/responseCatalog.json"))); - - givenThat(post(urlPathEqualTo(pathNegotiate)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", contentType) - .withBodyFile( - "edc/responseStartNegotiation.json"))); - - final var negotiationId = "1bbaec6e-c316-4e1e-8258-c07a648cc43c"; - givenThat(get(urlPathEqualTo(pathNegotiate + "/" + negotiationId)).willReturn(aResponse().withStatus(200) - .withHeader( - "Content-Type", - contentType) - .withBodyFile( - "edc/responseGetNegotiationConfirmed.json"))); - givenThat(get(urlPathEqualTo(pathNegotiate + "/" + negotiationId + "/state")).willReturn( - aResponse().withStatus(200) - .withHeader("Content-Type", contentType) - .withBodyFile("edc/responseGetNegotiationState.json"))); - - givenThat(post(urlPathEqualTo(pathTransfer)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", contentType) - .withBodyFile( - "edc/responseStartTransferprocess.json"))); - final var transferProcessId = "1b21e963-0bc5-422a-b30d-fd3511861d88"; - givenThat(get(urlPathEqualTo(pathTransfer + "/" + transferProcessId + "/state")).willReturn( - aResponse().withStatus(200) - .withHeader("Content-Type", contentType) - .withBodyFile("edc/responseGetTransferState.json"))); - givenThat(get(urlPathEqualTo(pathTransfer + "/" + transferProcessId)).willReturn(aResponse().withStatus(200) - .withHeader( - "Content-Type", - contentType) - .withBodyFile( - "edc/responseGetTransferConfirmed.json"))); - - final var contractAgreementId = "7681f966-36ea-4542-b5ea-0d0db81967de:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-31b614f5-ec14-4ed2-a509-e7b7780083e7:a6144a2e-c1b1-4ec6-96e1-a221da134e4f"; - final EndpointDataReference ref = EndpointDataReference.Builder.newInstance() - .authKey("testkey") - .authCode("testcode") - .properties(Map.of(NAMESPACE_EDC_CID, - contractAgreementId)) - .endpoint("http://provider.dataplane/api/public") - .build(); - storage.put(contractAgreementId, ref); - } - @Test void shouldReturnMaterialForRecyclingAsString() throws EdcClientException, ExecutionException, InterruptedException { // Arrange prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBodyFile( - "materialForRecycling.json"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn( + responseWithStatus(200).withBodyFile("materialForRecycling.json"))); // Act - final String submodel = edcSubmodelClient.getSubmodelRawPayload(connectorEndpoint, buildWiremockDataplaneUrl(), - assetId).get(); + final String submodel = edcSubmodelClient.getSubmodelPayload(CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, + ASSET_ID).get().getPayload(); // Assert assertThat(submodel).contains("\"materialName\": \"Cooper\","); @@ -236,14 +177,11 @@ void shouldReturnObjectAsStringWhenResponseNotJSON() throws EdcClientException, ExecutionException, InterruptedException { // Arrange prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBody("test"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn(responseWithStatus(200).withBody("test"))); // Act - final String submodel = edcSubmodelClient.getSubmodelRawPayload(connectorEndpoint, buildWiremockDataplaneUrl(), - assetId).get(); + final String submodel = edcSubmodelClient.getSubmodelPayload(CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, + ASSET_ID).get().getPayload(); // Assert assertThat(submodel).isEqualTo("test"); @@ -253,25 +191,22 @@ void shouldReturnObjectAsStringWhenResponseNotJSON() void shouldThrowExceptionWhenPoliciesAreNotAccepted() { // Arrange final List andConstraints = List.of( - new Constraint("Membership", OperatorType.EQ, List.of("active"))); + new Constraint("Membership", new Operator(OperatorType.EQ), "active")); final ArrayList orConstraints = new ArrayList<>(); final Permission permission = new Permission(PolicyType.USE, - List.of(new Constraints(andConstraints, orConstraints))); + new Constraints(andConstraints, orConstraints)); final AcceptedPolicy acceptedPolicy = new AcceptedPolicy(policy("IRS Policy", List.of(permission)), OffsetDateTime.now().plusYears(1)); when(acceptedPoliciesProvider.getAcceptedPolicies()).thenReturn(List.of(acceptedPolicy)); prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBody("test"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn(responseWithStatus(200).withBody("test"))); // Act & Assert final String errorMessage = "Consumption of asset '58505404-4da1-427a-82aa-b79482bcd1f0' is not permitted as the required catalog offer policies do not comply with defined IRS policies."; assertThatExceptionOfType(UsagePolicyException.class).isThrownBy( - () -> edcSubmodelClient.getSubmodelRawPayload(connectorEndpoint, buildWiremockDataplaneUrl(), assetId) + () -> edcSubmodelClient.getSubmodelPayload(CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, ASSET_ID) .get()).withMessageEndingWith(errorMessage); } @@ -279,14 +214,12 @@ void shouldThrowExceptionWhenPoliciesAreNotAccepted() { void shouldThrowExceptionWhenResponse_400() { // Arrange prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(400) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBody("{ error: '400'}"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn( + responseWithStatus(400).withBody("{ error: '400'}"))); // Act - final ThrowableAssert.ThrowingCallable throwingCallable = () -> edcSubmodelClient.getSubmodelRawPayload( - connectorEndpoint, buildWiremockDataplaneUrl(), assetId).get(5, TimeUnit.SECONDS); + final ThrowableAssert.ThrowingCallable throwingCallable = () -> edcSubmodelClient.getSubmodelPayload( + CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, ASSET_ID).get(5, TimeUnit.SECONDS); // Assert assertThatExceptionOfType(ExecutionException.class).isThrownBy(throwingCallable) @@ -297,26 +230,41 @@ void shouldThrowExceptionWhenResponse_400() { void shouldThrowExceptionWhenResponse_500() { // Arrange prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(500) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBody("{ error: '500'}"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn( + responseWithStatus(500).withBody("{ error: '500'}"))); // Act - final ThrowableAssert.ThrowingCallable throwingCallable = () -> edcSubmodelClient.getSubmodelRawPayload( - connectorEndpoint, buildWiremockDataplaneUrl(), assetId).get(5, TimeUnit.SECONDS); + final ThrowableAssert.ThrowingCallable throwingCallable = () -> edcSubmodelClient.getSubmodelPayload( + CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, ASSET_ID).get(5, TimeUnit.SECONDS); // Assert assertThatExceptionOfType(ExecutionException.class).isThrownBy(throwingCallable) .withCauseInstanceOf(RestClientException.class); } - private String buildWiremockDataplaneUrl() { - return buildApiMethodUrl() + submodelDataplanePath; + private void prepareNegotiation() { + final String contractAgreementId = SubmodelFacadeWiremockSupport.prepareNegotiation(); + final EndpointDataReference ref = createEndpointDataReference(contractAgreementId); + storage.put(contractAgreementId, ref); } - private String buildApiMethodUrl() { - return String.format("http://localhost:%d", this.wireMockServer.port()); + public static EndpointDataReference createEndpointDataReference(final String contractAgreementId) { + final EDRAuthCode edrAuthCode = EDRAuthCode.builder() + .cid(contractAgreementId) + .dad("test") + .exp(9999999999L) + .build(); + final String b64EncodedAuthCode = Base64.getUrlEncoder() + .encodeToString(StringMapper.mapToString(edrAuthCode) + .getBytes(StandardCharsets.UTF_8)); + final String jwtToken = "eyJhbGciOiJSUzI1NiJ9." + b64EncodedAuthCode + ".test"; + return EndpointDataReference.Builder.newInstance() + .authKey("testkey") + .authCode(jwtToken) + .properties( + Map.of(JsonLdConfiguration.NAMESPACE_EDC_CID, contractAgreementId)) + .endpoint(DATAPLANE_HOST + PATH_DATAPLANE_PUBLIC) + .build(); } private org.eclipse.tractusx.irs.edc.client.policy.Policy policy(String policyId, List permissions) { diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelRetryerTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelRetryerTest.java index 3ac9de4f3b..7758811e42 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelRetryerTest.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelRetryerTest.java @@ -97,7 +97,7 @@ void shouldRetryExecutionOfGetSubmodelOnClientMaxAttemptTimes() { when(endpointDataReferenceCacheService.getEndpointDataReference("9300395e-c0a5-4e88-bc57-a3973fec4c26")).thenReturn(new EndpointDataReferenceStatus(null, EndpointDataReferenceStatus.TokenStatus.REQUIRED_NEW)); // Act - assertThatThrownBy(() -> testee.getSubmodelRawPayload( + assertThatThrownBy(() -> testee.getSubmodelPayload( "https://connector.endpoint.com", "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", "9300395e-c0a5-4e88-bc57-a3973fec4c26")).hasCauseInstanceOf( @@ -116,7 +116,7 @@ void shouldRetryOnAnyRuntimeException() { when(endpointDataReferenceCacheService.getEndpointDataReference("9300395e-c0a5-4e88-bc57-a3973fec4c26")).thenReturn(new EndpointDataReferenceStatus(null, EndpointDataReferenceStatus.TokenStatus.REQUIRED_NEW)); // Act - assertThatThrownBy(() -> testee.getSubmodelRawPayload( + assertThatThrownBy(() -> testee.getSubmodelPayload( "https://connector.endpoint.com", "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", "9300395e-c0a5-4e88-bc57-a3973fec4c26")).hasCauseInstanceOf(RuntimeException.class); diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/policy/ConstraintCheckerServiceTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/policy/ConstraintCheckerServiceTest.java index 4d3955f690..5e03e8e8d7 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/policy/ConstraintCheckerServiceTest.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/policy/ConstraintCheckerServiceTest.java @@ -168,8 +168,8 @@ void shouldRejectOrConstraintIfAnyMatch() { private Policy createPolicyWithAndConstraint(List operands) { List and = operands.stream() - .map(operand -> new Constraint(operand.left, OperatorType.EQ, - List.of(operand.right))) + .map(operand -> new Constraint(operand.left, new Operator(OperatorType.EQ), + operand.right)) .toList(); Constraints constraints = new Constraints(and, new ArrayList<>()); return createPolicyWithConstraint(constraints); @@ -177,16 +177,15 @@ private Policy createPolicyWithAndConstraint(List operands) { private Policy createPolicyWithOrConstraint(List operands) { List or = operands.stream() - .map(operand -> new Constraint(operand.left, OperatorType.EQ, - List.of(operand.right))) + .map(operand -> new Constraint(operand.left, new Operator(OperatorType.EQ), + operand.right)) .toList(); Constraints constraints = new Constraints(new ArrayList<>(), or); return createPolicyWithConstraint(constraints); } private Policy createPolicyWithConstraint(Constraints constraints) { - List constraintsList = List.of(constraints); - Permission permission = new Permission(PolicyType.ACCESS, constraintsList); + Permission permission = new Permission(PolicyType.ACCESS, constraints); List permissions = List.of(permission); final String policyId = "policyId"; return new Policy(policyId, OffsetDateTime.now(), OffsetDateTime.now().plusYears(1), permissions); diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/policy/PolicyCheckerServiceTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/policy/PolicyCheckerServiceTest.java index bf02707687..1954360597 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/policy/PolicyCheckerServiceTest.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/policy/PolicyCheckerServiceTest.java @@ -106,12 +106,12 @@ void shouldRejectAndConstraintsWhenOnlyOneMatch() { @Test void shouldAcceptAndConstraintsWhenAcceptedPolicyContainsMoreConstraintsSuperSetOfProvidedPolicy() { // given - final Constraint constraint1 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_TRACEABILITY, OperatorType.EQ, - List.of(TestConstants.STATUS_ACTIVE)); - final Constraint constraint2 = new Constraint(TestConstants.MEMBERSHIP, OperatorType.EQ, - List.of(TestConstants.STATUS_ACTIVE)); - final Constraint constraint3 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_DISMANTLER, OperatorType.EQ, - List.of(TestConstants.STATUS_ACTIVE)); + final Constraint constraint1 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_TRACEABILITY, new Operator(OperatorType.EQ), + TestConstants.STATUS_ACTIVE); + final Constraint constraint2 = new Constraint(TestConstants.MEMBERSHIP, new Operator(OperatorType.EQ), + TestConstants.STATUS_ACTIVE); + final Constraint constraint3 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_DISMANTLER, new Operator(OperatorType.EQ), + TestConstants.STATUS_ACTIVE); final var policyList = List.of( new AcceptedPolicy(policy("and-policy", List.of(constraint1, constraint2, constraint3), List.of()), OffsetDateTime.now().plusYears(1))); @@ -131,12 +131,12 @@ void shouldAcceptAndConstraintsWhenAcceptedPolicyContainsMoreConstraintsSuperSet @Test void shouldAcceptOrConstraintsWhenAcceptedPolicyContainsMoreConstraintsSuperSetOfProvidedPolicy() { // given - final Constraint constraint1 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_TRACEABILITY, OperatorType.EQ, - List.of(TestConstants.STATUS_ACTIVE)); - final Constraint constraint2 = new Constraint(TestConstants.MEMBERSHIP, OperatorType.EQ, - List.of(TestConstants.STATUS_ACTIVE)); - final Constraint constraint3 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_DISMANTLER, OperatorType.EQ, - List.of(TestConstants.STATUS_ACTIVE)); + final Constraint constraint1 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_TRACEABILITY, new Operator(OperatorType.EQ), + TestConstants.STATUS_ACTIVE); + final Constraint constraint2 = new Constraint(TestConstants.MEMBERSHIP, new Operator(OperatorType.EQ), + TestConstants.STATUS_ACTIVE); + final Constraint constraint3 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_DISMANTLER, new Operator(OperatorType.EQ), + TestConstants.STATUS_ACTIVE); final var policyList = List.of( new AcceptedPolicy(policy("and-policy", List.of(), List.of(constraint1, constraint2, constraint3)), OffsetDateTime.now().plusYears(1))); @@ -156,12 +156,12 @@ void shouldAcceptOrConstraintsWhenAcceptedPolicyContainsMoreConstraintsSuperSetO @Test void shouldAcceptConstraintsWithDefaultPolicy() { // given - final Constraint constraint1 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_TRACEABILITY, OperatorType.EQ, - List.of(TestConstants.STATUS_ACTIVE)); - final Constraint constraint2 = new Constraint(TestConstants.MEMBERSHIP, OperatorType.EQ, - List.of(TestConstants.STATUS_ACTIVE)); - final Constraint constraint3 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_DISMANTLER, OperatorType.EQ, - List.of(TestConstants.STATUS_ACTIVE)); + final Constraint constraint1 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_TRACEABILITY, new Operator(OperatorType.EQ), + TestConstants.STATUS_ACTIVE); + final Constraint constraint2 = new Constraint(TestConstants.MEMBERSHIP, new Operator(OperatorType.EQ), + TestConstants.STATUS_ACTIVE); + final Constraint constraint3 = new Constraint(TestConstants.FRAMEWORK_AGREEMENT_DISMANTLER, new Operator(OperatorType.EQ), + TestConstants.STATUS_ACTIVE); final var policyList = List.of(new AcceptedPolicy( policy("default-policy", List.of(constraint1, constraint2, constraint3), @@ -242,7 +242,7 @@ private org.eclipse.tractusx.irs.edc.client.policy.Policy policy(final String po private org.eclipse.tractusx.irs.edc.client.policy.Policy policy(final String policyId, final List andConstraint, final List orConstraint) { - final List constraints = List.of(new Constraints(andConstraint, orConstraint)); + final Constraints constraints = new Constraints(andConstraint, orConstraint); final List permissions = List.of(new Permission(PolicyType.USE, constraints)); return new org.eclipse.tractusx.irs.edc.client.policy.Policy(policyId, OffsetDateTime.now(), OffsetDateTime.now().plusYears(1), permissions); diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/testutil/TestMother.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/testutil/TestMother.java index 72e368dc6a..973b10b22a 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/testutil/TestMother.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/testutil/TestMother.java @@ -33,6 +33,8 @@ import static org.eclipse.tractusx.irs.edc.client.configuration.JsonLdConfiguration.NAMESPACE_TRACTUSX; import static org.mockito.Mockito.mock; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.List; import java.util.Map; import java.util.UUID; @@ -48,6 +50,7 @@ import org.eclipse.edc.catalog.spi.DataService; import org.eclipse.edc.catalog.spi.Dataset; import org.eclipse.edc.catalog.spi.Distribution; +import org.eclipse.edc.core.transform.TypeTransformerRegistryImpl; import org.eclipse.edc.jsonld.TitaniumJsonLd; import org.eclipse.edc.policy.model.Action; import org.eclipse.edc.policy.model.AndConstraint; @@ -60,6 +63,10 @@ import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.XoneConstraint; import org.eclipse.edc.spi.monitor.ConsoleMonitor; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.irs.data.StringMapper; +import org.eclipse.tractusx.irs.edc.client.configuration.JsonLdConfiguration; +import org.eclipse.tractusx.irs.edc.client.model.EDRAuthCode; import org.eclipse.tractusx.irs.edc.client.transformer.EdcTransformer; import org.jetbrains.annotations.NotNull; @@ -73,7 +80,7 @@ public static EdcTransformer createEdcTransformer() { titaniumJsonLd.registerNamespace("edc", NAMESPACE_EDC); titaniumJsonLd.registerNamespace("dcat", NAMESPACE_DCAT); titaniumJsonLd.registerNamespace("dspace", NAMESPACE_DSPACE); - return new EdcTransformer(objectMapper(), titaniumJsonLd); + return new EdcTransformer(objectMapper(), titaniumJsonLd, new TypeTransformerRegistryImpl()); } public static ObjectMapper objectMapper() { @@ -149,4 +156,26 @@ public static Policy createXOneConstraintPolicy(final List constrain final Permission permission = createUsePermission(orConstraint); return Policy.Builder.newInstance().permission(permission).build(); } + + public static EndpointDataReference endpointDataReference(final String contractAgreementId) { + return EndpointDataReference.Builder.newInstance() + .authKey("testkey") + .authCode(edrAuthCode(contractAgreementId)) + .properties( + Map.of(JsonLdConfiguration.NAMESPACE_EDC_CID, contractAgreementId)) + .endpoint("http://provider.dataplane/api/public") + .build(); + } + + public static String edrAuthCode(final String contractAgreementId) { + final EDRAuthCode edrAuthCode = EDRAuthCode.builder() + .cid(contractAgreementId) + .dad("test") + .exp(9999999999L) + .build(); + final String b64EncodedAuthCode = Base64.getUrlEncoder() + .encodeToString(StringMapper.mapToString(edrAuthCode) + .getBytes(StandardCharsets.UTF_8)); + return "eyJhbGciOiJSUzI1NiJ9." + b64EncodedAuthCode + ".test"; + } } diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/transformer/EdcTransformerTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/transformer/EdcTransformerTest.java index 7d982c5902..bd9a1af69e 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/transformer/EdcTransformerTest.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/transformer/EdcTransformerTest.java @@ -42,6 +42,7 @@ import org.eclipse.edc.catalog.spi.DataService; import org.eclipse.edc.catalog.spi.Dataset; import org.eclipse.edc.catalog.spi.Distribution; +import org.eclipse.edc.core.transform.TypeTransformerRegistryImpl; import org.eclipse.edc.jsonld.TitaniumJsonLd; import org.eclipse.edc.policy.model.Action; import org.eclipse.edc.policy.model.AtomicConstraint; @@ -211,7 +212,7 @@ void setUp() { jsonLd.registerNamespace("dspace", "https://w3id.org/dspace/v0.8/"); ObjectMapper objectMapper = objectMapper(); - edcTransformer = new EdcTransformer(objectMapper, jsonLd); + edcTransformer = new EdcTransformer(objectMapper, jsonLd, new TypeTransformerRegistryImpl()); } @Test diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/transformer/JsonObjectToPolicyTransformerTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/transformer/JsonObjectToPolicyTransformerTest.java new file mode 100644 index 0000000000..1ffb52c740 --- /dev/null +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/transformer/JsonObjectToPolicyTransformerTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.edc.client.transformer; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.StringReader; +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; +import org.eclipse.edc.core.transform.TransformerContextImpl; +import org.eclipse.edc.core.transform.TypeTransformerRegistryImpl; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.eclipse.tractusx.irs.edc.client.policy.Constraint; +import org.eclipse.tractusx.irs.edc.client.policy.OperatorType; +import org.eclipse.tractusx.irs.edc.client.policy.Permission; +import org.eclipse.tractusx.irs.edc.client.policy.Policy; +import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class JsonObjectToPolicyTransformerTest { + + public static final String EXAMPLE_PAYLOAD = """ + { + "@context": { + "odrl": "http://www.w3.org/ns/odrl/2/" + }, + "@id": "policy-id", + "policy": { + "odrl:permission": [ + { + "odrl:action": "USE", + "odrl:constraint": { + "odrl:and": [ + { + "odrl:leftOperand": "Membership", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "active" + }, + { + "odrl:leftOperand": "PURPOSE", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "ID 3.1 Trace" + } + ] + } + } + ] + } + } + """; + + private JsonObjectToPolicyTransformer jsonObjectToPolicyTransformer; + private TransformerContext transformerContext; + + @BeforeEach + public void setUp() { + jsonObjectToPolicyTransformer = new JsonObjectToPolicyTransformer(new ObjectMapper()); + transformerContext = new TransformerContextImpl(new TypeTransformerRegistryImpl()); + } + + @Test + void shouldTransformJsonObjectToPolicyCorrectly() { + // given + JsonReader jsonReader = Json.createReader(new StringReader(EXAMPLE_PAYLOAD)); + JsonObject jsonObject = jsonReader.readObject(); + jsonReader.close(); + + // when + final Policy transformed = jsonObjectToPolicyTransformer.transform(jsonObject, transformerContext); + + // then + assertThat(transformed.getPolicyId()).isEqualTo("policy-id"); + final Permission permission = transformed.getPermissions().get(0); + assertThat(permission.getAction()).isEqualTo(PolicyType.USE); + final List and = permission.getConstraint().getAnd(); + final Constraint and1 = and.get(0); + final Constraint and2 = and.get(1); + + assertThat(and1.getLeftOperand()).isEqualTo("Membership"); + assertThat(and1.getRightOperand()).isEqualTo("active"); + assertThat(and1.getOperator().getOperatorType()).isEqualTo(OperatorType.EQ); + + assertThat(and2.getLeftOperand()).isEqualTo("PURPOSE"); + assertThat(and2.getRightOperand()).isEqualTo("ID 3.1 Trace"); + assertThat(and2.getOperator().getOperatorType()).isEqualTo(OperatorType.EQ); + } +} \ No newline at end of file diff --git a/irs-integration-tests/src/test/java/org/eclipse/tractusx/irs/smoketest/ItemGraphSmokeTest.java b/irs-integration-tests/src/test/java/org/eclipse/tractusx/irs/smoketest/ItemGraphSmokeTest.java index 3421228bd1..47298d74c1 100644 --- a/irs-integration-tests/src/test/java/org/eclipse/tractusx/irs/smoketest/ItemGraphSmokeTest.java +++ b/irs-integration-tests/src/test/java/org/eclipse/tractusx/irs/smoketest/ItemGraphSmokeTest.java @@ -43,7 +43,7 @@ import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; import org.eclipse.tractusx.irs.component.RegisterJob; import org.eclipse.tractusx.irs.component.Relationship; -import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.assetadministrationshell.SubmodelDescriptor; import org.eclipse.tractusx.irs.component.enums.AspectType; import org.eclipse.tractusx.irs.component.enums.BomLifecycle; @@ -178,8 +178,8 @@ void shouldCreateAndCompleteJob() { assertThat(completedJobs.getTombstones().size()).isNotNegative(); assertThat(completedJobs.getBpns()).isNotEmpty(); - final AssetAdministrationShellDescriptor assDescriptor = completedJobs.getShells().get(0); - final List submodelDescriptors = assDescriptor.getSubmodelDescriptors(); + final Shell assDescriptor = completedJobs.getShells().get(0); + final List submodelDescriptors = assDescriptor.payload().getSubmodelDescriptors(); assertThat(submodelDescriptors).isNotEmpty(); } diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/GenericDescription.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/GenericDescription.java deleted file mode 100644 index 197ee49740..0000000000 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/GenericDescription.java +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022,2024 - * 2022: ZF Friedrichshafen AG - * 2022: ISTOS GmbH - * 2022,2024: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * 2022,2023: BOSCH AG - * Copyright (c) 2021,2024 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.irs.component; - -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; -import lombok.Value; - -/** - * GenericDescription - */ -@Schema(description = "") -@Value -@Builder(toBuilder = true) -@JsonDeserialize(builder = GenericDescription.GenericDescriptionBuilder.class) -public class GenericDescription { - - @Schema(description = "Identification string") - private String identification; - - @Schema(description = "Identification short form") - private String idShort; - - @Schema(description = "Key value pair for specific asset id") - private Map specificAssetId; - - @Schema(description = "Description") - private List descriptions; -} diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/JobParameter.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/JobParameter.java index 38eabd73dd..a06333f1a1 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/JobParameter.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/JobParameter.java @@ -72,6 +72,9 @@ public class JobParameter { @Schema(implementation = Boolean.class, example = "false") private boolean lookupBPNs; + @Schema(implementation = Boolean.class, example = "false") + private boolean auditContractNegotiation; + @Schema(implementation = String.class, example = "https://hostname.com/callback?id={id}&state={state}") private String callbackUrl; @@ -91,6 +94,7 @@ public static JobParameter create(final @NonNull RegisterJob request) { : aspectTypeValues) .collectAspects(request.isCollectAspects()) .lookupBPNs(request.isLookupBPNs()) + .auditContractNegotiation(request.isAuditContractNegotiation()) .callbackUrl(request.getCallbackUrl()) .build(); } diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Jobs.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Jobs.java index 4dd413f1e2..42b53f46b5 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Jobs.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Jobs.java @@ -33,7 +33,6 @@ import lombok.Singular; import lombok.Value; import lombok.extern.jackson.Jacksonized; -import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; /** * List of Job and relationship to parts @@ -54,7 +53,7 @@ public class Jobs { private List relationships; @ArraySchema(arraySchema = @Schema(description = "AAS shells."), maxItems = Integer.MAX_VALUE) - private List shells; + private List shells; @ArraySchema(arraySchema = @Schema(description = "Collection of not resolvable endpoints as tombstones. Including cause of error and endpoint URL."), maxItems = Integer.MAX_VALUE) @Singular diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/ProtocolInformation.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/ProtocolInformation.java deleted file mode 100644 index 7b5d6eab57..0000000000 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/ProtocolInformation.java +++ /dev/null @@ -1,49 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022,2024 - * 2022: ZF Friedrichshafen AG - * 2022: ISTOS GmbH - * 2022,2024: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * 2022,2023: BOSCH AG - * Copyright (c) 2021,2024 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.irs.component; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; -import lombok.Value; -import lombok.extern.jackson.Jacksonized; - -/** - * ProtocolInformation - */ -@Value -@Jacksonized -@Builder(toBuilder = true) -public class ProtocolInformation { - - @Schema(description = "Uniform resource identifier of endpoint.", - example = "https://catena-x.net/vehicle/basedetails/", implementation = java.net.URI.class) - private String endpointAddress; - - @Schema(description = "Protocol used to access the endpoint.", example = "HTTP or HTTPS", - implementation = String.class) - private String endpointProtocol; - - @Schema(description = "Protocol version.", example = "1.0", implementation = String.class) - private String enpointProtocolVersion; -} diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterJob.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterJob.java index 87b574b47c..82b978ab50 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterJob.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterJob.java @@ -81,6 +81,9 @@ public class RegisterJob { @Schema(description = "Flag to specify whether BPNs should be collected and resolved via the configured BPDM URL. Default is false.") private boolean lookupBPNs; + @Schema(description = "Flag enables and disables auditing, including provisioning of ContractAgreementId inside submodels and shells objects. Default is true.") + private boolean auditContractNegotiation = true; + @URL(regexp = "^(http|https).*") @Schema(description = "Callback url to notify requestor when job processing is finished. There are two uri variable placeholders that can be used: id and state.", example = "https://hostname.com/callback?id={id}&state={state}") diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Shell.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Shell.java index 68063e1bae..dc0284159c 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Shell.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Shell.java @@ -23,54 +23,19 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.component; -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Singular; -import lombok.Value; +import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; /** - * An AAS shell. + * Shell container + * + * @param contractAgreementId + * @param payload */ -@Value -@Builder(toBuilder = true) -@Schema(description = "") -@AllArgsConstructor -@JsonDeserialize(builder = Shell.ShellBuilder.class) -public class Shell { - - @Schema(implementation = String.class) - private String identification; - - @Schema(implementation = String.class) - private String idShort; - - @Schema() - @Singular - private Map specificAssetIds; - - @Schema() - @Singular - private List descriptions; - - @Schema() - @Singular - private List globalAssetIds; - - @Schema() - @Singular - private List submodelDescriptors; +@Builder +public record Shell(String contractAgreementId, AssetAdministrationShellDescriptor payload) { - /** - * User to build Shell - */ - @Schema(description = "User to build shell items") - @JsonPOJOBuilder(withPrefix = "") - public static class ShellBuilder { + public Shell withoutContractAgreementId() { + return Shell.builder().payload(this.payload()).build(); } } diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Submodel.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Submodel.java index f3b9a8dccf..38df73789e 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Submodel.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Submodel.java @@ -40,12 +40,14 @@ public class Submodel { private String identification; private String aspectType; + private String contractAgreementId; private Map payload; - public static Submodel from(final String identification, final String aspectType, final Map payload) { + public static Submodel from(final String identification, final String aspectType, final String contractAgreementId, final Map payload) { return Submodel.builder() .identification(identification) .aspectType(aspectType) + .contractAgreementId(contractAgreementId) .payload(payload) .build(); } diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/SubmodelDescriptor.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/SubmodelDescriptor.java deleted file mode 100644 index f762fffa38..0000000000 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/SubmodelDescriptor.java +++ /dev/null @@ -1,69 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022,2024 - * 2022: ZF Friedrichshafen AG - * 2022: ISTOS GmbH - * 2022,2024: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * 2022,2023: BOSCH AG - * Copyright (c) 2021,2024 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.irs.component; - -import java.util.List; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Singular; -import lombok.Value; - -/** - * SubmodelDescriptor description - */ -@Value -@Builder(toBuilder = true) -@AllArgsConstructor -@JsonDeserialize(builder = SubmodelDescriptor.SubmodelDescriptorBuilder.class) -public class SubmodelDescriptor { - - @Schema(implementation = String.class) - private String identification; - - @Schema(implementation = String.class) - private String idShort; - - @Schema() - @Singular - private List descriptions; - - @Schema(implementation = SemanticId.class) - private SemanticId semanticId; - - @Schema() - @Singular - private List endpoints; - - /** - * User to build SubmodelDescriptor - */ - @Schema(description = "User to build async fetched items") - @JsonPOJOBuilder(withPrefix = "") - public static class SubmodelDescriptorBuilder { - } -} diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java index f34a6901a0..268a33c8c4 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java @@ -25,6 +25,7 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.Map; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @@ -51,14 +52,25 @@ public class Tombstone { private final String catenaXId; private final String endpointURL; private final ProcessingError processingError; + private final Map policy; public static Tombstone from(final String catenaXId, final String endpointURL, final Exception exception, final int retryCount, final ProcessStep processStep) { - return from(catenaXId, endpointURL, exception.getMessage(), retryCount, processStep); + return from(catenaXId, endpointURL, exception.getMessage(), retryCount, processStep, null); } public static Tombstone from(final String catenaXId, final String endpointURL, final String errorDetails, final int retryCount, final ProcessStep processStep) { + return from(catenaXId, endpointURL, errorDetails, retryCount, processStep, null); + } + + public static Tombstone from(final String catenaXId, final String endpointURL, final Exception exception, + final int retryCount, final ProcessStep processStep, final Map policy) { + return from(catenaXId, endpointURL, exception.getMessage(), retryCount, processStep, policy); + } + + public static Tombstone from(final String catenaXId, final String endpointURL, final String errorDetails, + final int retryCount, final ProcessStep processStep, final Map policy) { final ProcessingError processingError = ProcessingError.builder() .withProcessStep(processStep) @@ -70,6 +82,8 @@ public static Tombstone from(final String catenaXId, final String endpointURL, f .endpointURL(endpointURL) .catenaXId(catenaXId) .processingError(processingError) + .policy(policy) .build(); } + } diff --git a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/config/PolicyConfiguration.java b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/config/PolicyConfiguration.java index 3af3dae223..73ac5f8a85 100644 --- a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/config/PolicyConfiguration.java +++ b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/config/PolicyConfiguration.java @@ -23,6 +23,8 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.policystore.config; +import org.eclipse.edc.core.transform.TypeTransformerRegistryImpl; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.tractusx.irs.common.persistence.BlobPersistence; import org.eclipse.tractusx.irs.common.persistence.BlobPersistenceException; import org.eclipse.tractusx.irs.common.persistence.MinioBlobPersistence; @@ -44,4 +46,9 @@ public BlobPersistence blobStore(final PolicyBlobstoreConfiguration config) thro return new MinioBlobPersistence(config.getEndpoint(), config.getAccessKey(), config.getSecretKey(), config.getBucketName(), config.getDaysToLive()); } + + @Bean + public TypeTransformerRegistry typeTransformerRegistry() { + return new TypeTransformerRegistryImpl(); + } } diff --git a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreController.java b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreController.java index 2c18570427..ec06c135e2 100644 --- a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreController.java +++ b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreController.java @@ -27,8 +27,6 @@ import static org.eclipse.tractusx.irs.common.ApiConstants.UNAUTHORIZED_DESC; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import java.util.List; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; @@ -38,12 +36,15 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.common.auth.IrsRoles; import org.eclipse.tractusx.irs.dtos.ErrorResponse; import org.eclipse.tractusx.irs.edc.client.policy.Policy; +import org.eclipse.tractusx.irs.edc.client.transformer.EdcTransformer; import org.eclipse.tractusx.irs.policystore.models.CreatePolicyRequest; +import org.eclipse.tractusx.irs.policystore.models.PolicyResponse; import org.eclipse.tractusx.irs.policystore.models.UpdatePolicyRequest; import org.eclipse.tractusx.irs.policystore.services.PolicyStoreService; import org.springframework.http.HttpStatus; @@ -71,6 +72,7 @@ public class PolicyStoreController { private final PolicyStoreService service; + private final EdcTransformer edcTransformer; @Operation(operationId = "registerAllowedPolicy", summary = "Register a policy that should be accepted in EDC negotiation.", @@ -100,8 +102,10 @@ public class PolicyStoreController { @PostMapping("/policies") @ResponseStatus(HttpStatus.CREATED) @PreAuthorize("hasAuthority('" + IrsRoles.ADMIN_IRS + "')") - public void registerAllowedPolicy(final @Valid @RequestBody CreatePolicyRequest request) { - service.registerPolicy(request); + public void registerAllowedPolicy(final @RequestBody CreatePolicyRequest request) { + final Policy policy = edcTransformer.transformToPolicy(request.payload()); + policy.setValidUntil(request.validUntil()); + service.registerPolicy(policy); } @Operation(operationId = "getAllowedPolicies", @@ -129,8 +133,10 @@ public void registerAllowedPolicy(final @Valid @RequestBody CreatePolicyRequest @GetMapping("/policies") @ResponseStatus(HttpStatus.OK) @PreAuthorize("hasAuthority('" + IrsRoles.ADMIN_IRS + "')") - public List getPolicies() { - return service.getStoredPolicies(); + public List getPolicies() { + return service.getStoredPolicies().stream() + .map(PolicyResponse::fromPolicy) + .toList(); } @Operation(operationId = "deleteAllowedPolicy", diff --git a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/Context.java b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/Context.java new file mode 100644 index 0000000000..8e8c7c3a45 --- /dev/null +++ b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/Context.java @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.policystore.models; + +/** + * Context representation for get all policies response + */ +public record Context(String odrl) { + private static final String ODRL_VALUE = "http://www.w3.org/ns/odrl/2/"; + + public static Context getDefault() { + return new Context(ODRL_VALUE); + } +} diff --git a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/CreatePolicyRequest.java b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/CreatePolicyRequest.java index 3a3b014956..06fbee3f36 100644 --- a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/CreatePolicyRequest.java +++ b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/CreatePolicyRequest.java @@ -24,23 +24,57 @@ package org.eclipse.tractusx.irs.policystore.models; import java.time.OffsetDateTime; -import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.json.JsonObject; import jakarta.validation.constraints.NotNull; -import org.eclipse.tractusx.irs.edc.client.policy.Permission; /** - * Request object for policy creation - * - * @param policyId the ID of the policy - * @param validUntil the timestamp after which the policy should no longer be accepted + * Object for API to create policty */ +@SuppressWarnings("FileTabCharacter") @Schema(description = "Request to add a policy") -public record CreatePolicyRequest(@Schema(description = "The ID of the policy to add") @NotNull String policyId, - - @Schema(description = "Timestamp after which the policy will no longer be accepted in negotiations") @NotNull OffsetDateTime validUntil, - @Schema(description = "List of permissions that will be added to the Policy on creation.") @NotNull List permissions - ) { +public record CreatePolicyRequest( + @NotNull @Schema(description = "Timestamp after which the policy will no longer be accepted in negotiations") OffsetDateTime validUntil, + @NotNull @Schema(example = CreatePolicyRequest.EXAMPLE_PAYLOAD) JsonObject payload) { + @SuppressWarnings("java:S2479") + // this value is used by open-api to show example payload + // \u0009 character is required for this value to be correctly shown in open-api + public static final String EXAMPLE_PAYLOAD = """ + { + "validUntil": "2025-12-12T23:59:59.999Z", + "payload": { + "@context": { + "odrl": "http://www.w3.org/ns/odrl/2/" + }, + "@id": "policy-id", + "policy": { + "odrl:permission": [ + { + "odrl:action": "USE", + "odrl:constraint": { + "odrl:and": [ + { + "odrl:leftOperand": "Membership", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "active" + }, + { + "odrl:leftOperand": "PURPOSE", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "ID 3.1 Trace" + } + ] + } + } + ] + } + } + } + """; } diff --git a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/Payload.java b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/Payload.java new file mode 100644 index 0000000000..42a29dfde1 --- /dev/null +++ b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/Payload.java @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.policystore.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import org.eclipse.tractusx.irs.edc.client.policy.Policy; + +/** + * Payload representation for get all policies response + */ +@Builder +public record Payload( + @JsonProperty("@context") Context context, + @JsonProperty("@id") String policyId, + Policy policy +) { + +} diff --git a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/PolicyResponse.java b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/PolicyResponse.java new file mode 100644 index 0000000000..bf009dc8cf --- /dev/null +++ b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/PolicyResponse.java @@ -0,0 +1,86 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.policystore.models; + +import java.time.OffsetDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import org.eclipse.tractusx.irs.edc.client.policy.Policy; + +/** + * Policy representation for get all policies response + */ +@Builder +@Schema(example = PolicyResponse.EXAMPLE_PAYLOAD) +public record PolicyResponse(OffsetDateTime validUntil, Payload payload) { + + @SuppressWarnings({"FileTabCharacter", "java:S2479"}) + // required to show correctly example payload in open-api + public static final String EXAMPLE_PAYLOAD = """ + [ + { + "validUntil": "2025-12-12T23:59:59.999Z", + "payload": { + "@context": { + "odrl": "http://www.w3.org/ns/odrl/2/" + }, + "@id": "policy-id", + "policy": { + "odrl:permission": [ + { + "odrl:action": "USE", + "odrl:constraint": { + "odrl:and": [ + { + "odrl:leftOperand": "Membership", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "active" + }, + { + "odrl:leftOperand": "PURPOSE", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "ID 3.1 Trace" + } + ] + } + } + ] + } + } + } + ] + """; + + public static PolicyResponse fromPolicy(final Policy policy) { + return PolicyResponse.builder() + .validUntil(policy.getValidUntil()) + .payload(Payload.builder() + .policyId(policy.getPolicyId()) + .context(Context.getDefault()) + .policy(policy) + .build()) + .build(); + } +} diff --git a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/UpdatePolicyRequest.java b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/UpdatePolicyRequest.java index af6749ff85..4b4c455bd3 100644 --- a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/UpdatePolicyRequest.java +++ b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/models/UpdatePolicyRequest.java @@ -30,9 +30,8 @@ /** * Request object for policy update - * */ @Schema(description = "Request to add a policy") -public record UpdatePolicyRequest(@Schema(description = "Timestamp after which the policy will no longer be accepted in negotiations") @NotNull OffsetDateTime validUntil) { - +public record UpdatePolicyRequest( + @Schema(description = "Timestamp after which the policy will no longer be accepted in negotiations") @NotNull OffsetDateTime validUntil) { } diff --git a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/services/PolicyStoreService.java b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/services/PolicyStoreService.java index 7b73c249bb..3f16e4a90b 100644 --- a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/services/PolicyStoreService.java +++ b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/services/PolicyStoreService.java @@ -33,13 +33,13 @@ import org.eclipse.tractusx.irs.edc.client.policy.AcceptedPolicy; import org.eclipse.tractusx.irs.edc.client.policy.Constraint; import org.eclipse.tractusx.irs.edc.client.policy.Constraints; +import org.eclipse.tractusx.irs.edc.client.policy.Operator; import org.eclipse.tractusx.irs.edc.client.policy.OperatorType; import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.Policy; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; import org.eclipse.tractusx.irs.policystore.config.DefaultAcceptedPoliciesConfig; import org.eclipse.tractusx.irs.policystore.exceptions.PolicyStoreException; -import org.eclipse.tractusx.irs.policystore.models.CreatePolicyRequest; import org.eclipse.tractusx.irs.policystore.models.UpdatePolicyRequest; import org.eclipse.tractusx.irs.policystore.persistence.PolicyPersistence; import org.springframework.beans.factory.annotation.Value; @@ -56,32 +56,43 @@ public class PolicyStoreService implements AcceptedPoliciesProvider { public static final int DEFAULT_POLICY_LIFETIME_YEARS = 5; private final String apiAllowedBpn; - private final Clock clock; private final List allowedPoliciesFromConfig; private final PolicyPersistence persistence; + private final Clock clock; + private static final String MISSING_REQUEST_FIELD_MESSAGE = "Request does not contain all required fields. Missing: %s"; public PolicyStoreService(@Value("${apiAllowedBpn:}") final String apiAllowedBpn, - final DefaultAcceptedPoliciesConfig defaultAcceptedPoliciesConfig, final PolicyPersistence persistence, - final Clock clock) { + final DefaultAcceptedPoliciesConfig defaultAcceptedPoliciesConfig, final PolicyPersistence persistence, final Clock clock) { this.apiAllowedBpn = apiAllowedBpn; - this.allowedPoliciesFromConfig = createDefaultPolicyFromConfig(defaultAcceptedPoliciesConfig); - this.persistence = persistence; this.clock = clock; } - public void registerPolicy(final CreatePolicyRequest request) { - log.info("Registering new policy with id {}, valid until {}", request.policyId(), request.validUntil()); + public void registerPolicy(final Policy policy) { + validatePolicy(policy); + policy.setCreatedOn(OffsetDateTime.now(clock)); + log.info("Registering new policy with id {}, valid until {}", policy.getPolicyId(), policy.getValidUntil()); try { - persistence.save(apiAllowedBpn, - new Policy(request.policyId(), OffsetDateTime.now(clock), request.validUntil(), - request.permissions())); + persistence.save(apiAllowedBpn, policy); } catch (final PolicyStoreException e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage(), e); } } + /** + * Checks whether policy from register policy request has all required fields + * @param policy policy to register + */ + private void validatePolicy(final Policy policy) { + if (policy.getPermissions() == null) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.format(MISSING_REQUEST_FIELD_MESSAGE, "odrl:permission")); + } + if (policy.getPermissions().stream().anyMatch(p -> p.getConstraint() == null)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.format(MISSING_REQUEST_FIELD_MESSAGE, "odrl:constraint")); + } + } + public List getStoredPolicies() { log.info("Reading all stored polices for BPN {}", apiAllowedBpn); final var storedPolicies = persistence.readAll(apiAllowedBpn); @@ -121,15 +132,17 @@ private AcceptedPolicy toAcceptedPolicy(final Policy policy) { return new AcceptedPolicy(policy, policy.getValidUntil()); } - private List createDefaultPolicyFromConfig(final DefaultAcceptedPoliciesConfig defaultAcceptedPoliciesConfig) { + private List createDefaultPolicyFromConfig( + final DefaultAcceptedPoliciesConfig defaultAcceptedPoliciesConfig) { final List constraints = new ArrayList<>(); defaultAcceptedPoliciesConfig.getAcceptedPolicies() - .forEach(acceptedPolicy -> constraints.add(new Constraint(acceptedPolicy.getLeftOperand(), - OperatorType.fromValue(acceptedPolicy.getOperator()), - List.of(acceptedPolicy.getRightOperand())))); + .forEach(acceptedPolicy -> constraints.add( + new Constraint(acceptedPolicy.getLeftOperand(), + new Operator(OperatorType.fromValue(acceptedPolicy.getOperator())), + acceptedPolicy.getRightOperand()))); final Policy policy = new Policy("default-policy", OffsetDateTime.now(), OffsetDateTime.now().plusYears(DEFAULT_POLICY_LIFETIME_YEARS), - List.of(new Permission(PolicyType.USE, List.of(new Constraints(constraints, constraints))))); + List.of(new Permission(PolicyType.USE, new Constraints(constraints, constraints)))); return List.of(policy); } diff --git a/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreControllerTest.java b/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreControllerTest.java index dc967add7d..f685cd33ab 100644 --- a/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreControllerTest.java +++ b/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreControllerTest.java @@ -24,20 +24,32 @@ package org.eclipse.tractusx.irs.policystore.controllers; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.io.StringReader; import java.time.OffsetDateTime; import java.util.Collections; import java.util.List; - +import java.util.stream.Collectors; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; +import org.eclipse.edc.core.transform.TypeTransformerRegistryImpl; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.spi.monitor.ConsoleMonitor; import org.eclipse.tractusx.irs.edc.client.policy.Constraint; import org.eclipse.tractusx.irs.edc.client.policy.Constraints; +import org.eclipse.tractusx.irs.edc.client.policy.Operator; import org.eclipse.tractusx.irs.edc.client.policy.OperatorType; import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.Policy; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; +import org.eclipse.tractusx.irs.edc.client.transformer.EdcTransformer; import org.eclipse.tractusx.irs.policystore.models.CreatePolicyRequest; +import org.eclipse.tractusx.irs.policystore.models.PolicyResponse; import org.eclipse.tractusx.irs.policystore.models.UpdatePolicyRequest; import org.eclipse.tractusx.irs.policystore.services.PolicyStoreService; import org.junit.jupiter.api.BeforeEach; @@ -49,39 +61,85 @@ @ExtendWith(MockitoExtension.class) class PolicyStoreControllerTest { + public static final String EXAMPLE_PAYLOAD = """ + { + "validUntil": "2025-12-12T23:59:59.999Z", + "payload": { + "@context": { + "odrl": "http://www.w3.org/ns/odrl/2/" + }, + "@id": "policy-id", + "policy": { + "odrl:permission": [ + { + "odrl:action": "USE", + "odrl:constraint": { + "odrl:and": [ + { + "odrl:leftOperand": "Membership", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "active" + }, + { + "odrl:leftOperand": "PURPOSE", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "ID 3.1 Trace" + } + ] + } + } + ] + } + } + } + """; + private PolicyStoreController testee; + private final TitaniumJsonLd titaniumJsonLd = new TitaniumJsonLd(new ConsoleMonitor()); + private final EdcTransformer edcTransformer = new EdcTransformer(new com.fasterxml.jackson.databind.ObjectMapper(), + titaniumJsonLd, new TypeTransformerRegistryImpl()); @Mock private PolicyStoreService service; @BeforeEach void setUp() { - testee = new PolicyStoreController(service); + testee = new PolicyStoreController(service, edcTransformer); } @Test void registerAllowedPolicy() { // arrange - final CreatePolicyRequest request = new CreatePolicyRequest("policyId", OffsetDateTime.now(), createPermissions()); + final OffsetDateTime now = OffsetDateTime.now(); + JsonReader jsonReader = Json.createReader(new StringReader(EXAMPLE_PAYLOAD)); + JsonObject jsonObject = jsonReader.readObject(); + jsonReader.close(); // act - testee.registerAllowedPolicy(request); + testee.registerAllowedPolicy( + new CreatePolicyRequest(now.plusMinutes(1), jsonObject.get("payload").asJsonObject())); // assert - verify(service).registerPolicy(request); + verify(service).registerPolicy(any()); } @Test void getPolicies() { // arrange - final List policies = List.of(new Policy("testId", OffsetDateTime.now(), OffsetDateTime.now(), createPermissions())); + final List policies = List.of( + new Policy("testId", OffsetDateTime.now(), OffsetDateTime.now(), createPermissions())); when(service.getStoredPolicies()).thenReturn(policies); // act - final var returnedPolicies = testee.getPolicies(); + final List returnedPolicies = testee.getPolicies(); // assert - assertThat(returnedPolicies).isEqualTo(policies); + assertThat(returnedPolicies).isEqualTo( + policies.stream().map(PolicyResponse::fromPolicy).collect(Collectors.toList())); } @Test @@ -107,19 +165,14 @@ void updateAllowedPolicy() { } private List createPermissions() { - return List.of( - new Permission(PolicyType.USE, List.of(createConstraints())), - new Permission(PolicyType.ACCESS, List.of(createConstraints())) - ); + return List.of(new Permission(PolicyType.USE, createConstraints()), + new Permission(PolicyType.ACCESS, createConstraints())); } private Constraints createConstraints() { - return new Constraints( - Collections.emptyList(), - List.of( - new Constraint("Membership", OperatorType.EQ, List.of("active")), - new Constraint("FrameworkAgreement.traceability", OperatorType.EQ, List.of("active")), - new Constraint("PURPOSE", OperatorType.EQ, List.of("ID 3.1 Trace"))) - ); + return new Constraints(Collections.emptyList(), + List.of(new Constraint("Membership", new Operator(OperatorType.EQ), "active"), + new Constraint("FrameworkAgreement.traceability", new Operator(OperatorType.EQ), "active"), + new Constraint("PURPOSE", new Operator(OperatorType.EQ), "ID 3.1 Trace"))); } } \ No newline at end of file diff --git a/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/models/OperatorTypeTest.java b/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/models/OperatorTypeTest.java index 7cee4b39c6..4ea74c66f1 100644 --- a/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/models/OperatorTypeTest.java +++ b/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/models/OperatorTypeTest.java @@ -27,4 +27,28 @@ void shouldPrintProperString() { assertThat(OperatorType.GTEQ.toString()).isEqualTo(OperatorType.GTEQ.getCode()); } + @Test + void whenFromValueShouldRemovePrefixIfPresent() { + // given + final String prefix = "odrl:"; + final String operator = "eq"; + + // when + final OperatorType result = OperatorType.fromValue(prefix + operator); + + // then + assertThat(result.toString().toLowerCase()).isEqualTo(operator); + } + + @Test + void whenFromValueShouldNotRemovePrefixIfNotPresent() { + // given + final String operator = "eq"; + + // when + final OperatorType result = OperatorType.fromValue(operator); + + // then + assertThat(result.toString().toLowerCase()).isEqualTo(operator); + } } \ No newline at end of file diff --git a/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/services/PolicyStoreServiceTest.java b/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/services/PolicyStoreServiceTest.java index cec07264f0..e218ca2b94 100644 --- a/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/services/PolicyStoreServiceTest.java +++ b/irs-policy-store/src/test/java/org/eclipse/tractusx/irs/policystore/services/PolicyStoreServiceTest.java @@ -39,13 +39,13 @@ import org.eclipse.tractusx.irs.edc.client.policy.Constraint; import org.eclipse.tractusx.irs.edc.client.policy.Constraints; +import org.eclipse.tractusx.irs.edc.client.policy.Operator; import org.eclipse.tractusx.irs.edc.client.policy.OperatorType; import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.Policy; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; import org.eclipse.tractusx.irs.policystore.config.DefaultAcceptedPoliciesConfig; import org.eclipse.tractusx.irs.policystore.exceptions.PolicyStoreException; -import org.eclipse.tractusx.irs.policystore.models.CreatePolicyRequest; import org.eclipse.tractusx.irs.policystore.models.UpdatePolicyRequest; import org.eclipse.tractusx.irs.policystore.persistence.PolicyPersistence; import org.junit.jupiter.api.BeforeEach; @@ -80,10 +80,11 @@ void setUp() { @Test void registerPolicy() { // arrange - final var req = new CreatePolicyRequest("testId", OffsetDateTime.now(clock).plusMinutes(1), emptyList()); + final OffsetDateTime now = OffsetDateTime.now(clock); + final Policy policy = new Policy("testId", now, now.plusMinutes(1), emptyList()); // act - testee.registerPolicy(req); + testee.registerPolicy(policy); // assert verify(persistence).save(eq(BPN), any()); @@ -92,11 +93,11 @@ void registerPolicy() { @Test void registerPolicyWithPermission() { // arrange - final var req = new CreatePolicyRequest("testId", OffsetDateTime.now(clock).plusMinutes(1), - createPermissions()); + final OffsetDateTime now = OffsetDateTime.now(clock); + final Policy policy = new Policy("testId", now, now.plusMinutes(1), createPermissions()); // act - testee.registerPolicy(req); + testee.registerPolicy(policy); // assert verify(persistence).save(eq(BPN), policyCaptor.capture()); @@ -112,11 +113,12 @@ void registerPolicyWithPermission() { void registerPolicyShouldThrowResponseStatusException() { // act final String policyId = "testId"; + final OffsetDateTime now = OffsetDateTime.now(clock); + final Policy policy = new Policy(policyId, now, now.plusMinutes(1), createPermissions()); doThrow(new PolicyStoreException("")).when(persistence).save(eq(BPN), any()); - final CreatePolicyRequest request = new CreatePolicyRequest(policyId, OffsetDateTime.now(), emptyList()); // assert - assertThrows(ResponseStatusException.class, () -> testee.registerPolicy(request)); + assertThrows(ResponseStatusException.class, () -> testee.registerPolicy(policy)); } @Test @@ -150,10 +152,9 @@ void getDefaultStoredPoliciesWhenEmpty() { assertThat(defaultPolicies).hasSize(1); final List permissionList = defaultPolicies.get(0).getPermissions(); assertThat(permissionList).hasSize(1); - final List constraints = permissionList.get(0).getConstraints(); - assertThat(constraints).hasSize(1); - assertThat(constraints.get(0).getOr()).hasSize(2); - assertThat(constraints.get(0).getAnd()).hasSize(2); + final Constraints constraints = permissionList.get(0).getConstraint(); + assertThat(constraints.getOr()).hasSize(2); + assertThat(constraints.getAnd()).hasSize(2); } private Policy createPolicy(final String policyId) { @@ -161,15 +162,15 @@ private Policy createPolicy(final String policyId) { } private List createPermissions() { - return List.of(new Permission(PolicyType.USE, List.of(createConstraints())), - new Permission(PolicyType.ACCESS, List.of(createConstraints()))); + return List.of(new Permission(PolicyType.USE, createConstraints()), + new Permission(PolicyType.ACCESS, createConstraints())); } private Constraints createConstraints() { return new Constraints(Collections.emptyList(), - List.of(new Constraint("Membership", OperatorType.EQ, List.of("active")), - new Constraint("FrameworkAgreement.traceability", OperatorType.EQ, List.of("active")), - new Constraint(EXAMPLE_ACCEPTED_LEFT_OPERAND, OperatorType.EQ, List.of("ID 3.1 Trace")))); + List.of(new Constraint("Membership", new Operator(OperatorType.EQ), "active"), + new Constraint("FrameworkAgreement.traceability", new Operator(OperatorType.EQ), "active"), + new Constraint(EXAMPLE_ACCEPTED_LEFT_OPERAND, new Operator(OperatorType.EQ), "ID 3.1 Trace"))); } @Test @@ -213,4 +214,29 @@ void updatePolicyShouldThrowResponseStatusException() { // assert assertThrows(ResponseStatusException.class, () -> testee.updatePolicy(policyId, request)); } + + @Test + void whenRegisterPolicyWithMissingPermissionsShouldThrowException() { + // arrange + final Policy policy = new Policy(); + + // act + // assert + assertThrows(ResponseStatusException.class, () -> testee.registerPolicy(policy)); + } + + @Test + void whenRegisterPolicyWithMissingConstraintShouldThrowException() { + // arrange + final Policy policy = Policy.builder() + .permissions(List.of( + Permission.builder().constraint(new Constraints(emptyList(), emptyList())).build(), + Permission.builder().build() + )) + .build(); + + // act + // assert + assertThrows(ResponseStatusException.class, () -> testee.registerPolicy(policy)); + } } \ No newline at end of file diff --git a/irs-registry-client/pom.xml b/irs-registry-client/pom.xml index 9621ea237e..378022ac12 100644 --- a/irs-registry-client/pom.xml +++ b/irs-registry-client/pom.xml @@ -53,12 +53,23 @@ org.springframework.boot spring-boot-starter-web provided + + + snakeyaml + org.yaml + + org.yaml snakeyaml ${snakeyaml.version} + + org.eclipse.tractusx.irs + irs-common + ${revision} + org.eclipse.tractusx.irs irs-testing diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DigitalTwinRegistryService.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DigitalTwinRegistryService.java index 1afd1d3bb2..153acd3140 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DigitalTwinRegistryService.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DigitalTwinRegistryService.java @@ -25,7 +25,7 @@ import java.util.Collection; -import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.registryclient.exceptions.RegistryServiceException; /** @@ -39,7 +39,7 @@ public interface DigitalTwinRegistryService { * @param bpn the BPN to retrieve the shells for * @return the collection of asset administration shells */ - default Collection lookupShellsByBPN(final String bpn) throws RegistryServiceException { + default Collection lookupShellsByBPN(final String bpn) throws RegistryServiceException { return fetchShells(lookupShellIdentifiers(bpn)).stream().toList(); } @@ -69,6 +69,6 @@ default Collection lookupShells(final String bpn) throws * @param identifiers the shell identifiers * @return the shell descriptors */ - Collection fetchShells(Collection identifiers) + Collection fetchShells(Collection identifiers) throws RegistryServiceException; } diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryService.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryService.java index 78b709bd4d..538e8cfce6 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryService.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryService.java @@ -29,7 +29,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.assetadministrationshell.IdentifierKeyValuePair; import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryKey; import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryService; @@ -44,12 +44,12 @@ public class CentralDigitalTwinRegistryService implements DigitalTwinRegistrySer private final DigitalTwinRegistryClient digitalTwinRegistryClient; @Override - public Collection fetchShells(final Collection keys) { + public Collection fetchShells(final Collection keys) { return keys.stream().map(key -> { final String aaShellIdentification = getAAShellIdentificationOrGlobalAssetId(key.shellId()); log.info("Retrieved AAS Identification {} for globalAssetId {}", aaShellIdentification, key.shellId()); - return digitalTwinRegistryClient.getAssetAdministrationShellDescriptor(aaShellIdentification); + return new Shell("", digitalTwinRegistryClient.getAssetAdministrationShellDescriptor(aaShellIdentification)); }).toList(); } diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryService.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryService.java index f372c21668..9fb4065879 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryService.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryService.java @@ -23,19 +23,22 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.registryclient.decentral; -import java.time.Instant; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.irs.common.util.concurrent.ResultFinder; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; import org.eclipse.tractusx.irs.component.assetadministrationshell.IdentifierKeyValuePair; import org.eclipse.tractusx.irs.edc.client.model.EDRAuthCode; @@ -45,82 +48,174 @@ import org.eclipse.tractusx.irs.registryclient.exceptions.RegistryServiceException; import org.eclipse.tractusx.irs.registryclient.exceptions.ShellNotFoundException; import org.jetbrains.annotations.NotNull; +import org.springframework.util.StopWatch; /** * Decentral implementation of DigitalTwinRegistryService */ @RequiredArgsConstructor @Slf4j +@SuppressWarnings("PMD.TooManyMethods") public class DecentralDigitalTwinRegistryService implements DigitalTwinRegistryService { + private static final String TOOK_MS = "{} took {} ms"; + private final ConnectorEndpointsService connectorEndpointsService; private final EndpointDataForConnectorsService endpointDataForConnectorsService; private final DecentralDigitalTwinRegistryClient decentralDigitalTwinRegistryClient; + private ResultFinder resultFinder = new ResultFinder(); + private static Stream>> groupKeysByBpn( final Collection keys) { return keys.stream().collect(Collectors.groupingBy(DigitalTwinRegistryKey::bpn)).entrySet().stream(); } + /** + * Package private setter in order to allow simulating {@link InterruptedException} + * and {@link ExecutionException} in tests. + * + * @param resultFinder the {@link ResultFinder} + */ + /* package */ void setResultFinder(final ResultFinder resultFinder) { + this.resultFinder = resultFinder; + } + @Override - public Collection fetchShells(final Collection keys) + @SuppressWarnings("PMD.AvoidCatchingGenericException") + public Collection fetchShells(final Collection keys) throws RegistryServiceException { - log.info("Fetching shell(s) for {} key(s)", keys.size()); - final var calledEndpoints = new HashSet(); - final var collectedShells = groupKeysByBpn(keys).flatMap( - entry -> fetchShellDescriptors(calledEndpoints, entry.getKey(), entry.getValue())).toList(); - if (collectedShells.isEmpty()) { - throw new ShellNotFoundException("Unable to find any of the requested shells", calledEndpoints); - } else { - log.info("Found {} shell(s) for {} key(s)", collectedShells.size(), keys.size()); - return collectedShells; + + final var watch = new StopWatch(); + final String msg = "Fetching shell(s) for %s key(s)".formatted(keys.size()); + watch.start(msg); + log.info(msg); + + try { + final var calledEndpoints = new HashSet(); + + final var collectedShells = groupKeysByBpn(keys).flatMap(entry -> { + + try { + return fetchShellDescriptors(entry, calledEndpoints); + } catch (RuntimeException e) { + // catching generic exception is intended here, + // otherwise Jobs stay in state RUNNING forever + log.warn(e.getMessage(), e); + return Stream.empty(); + } + + }).toList(); + + if (collectedShells.isEmpty()) { + log.info("No shells found"); + throw new ShellNotFoundException("Unable to find any of the requested shells", calledEndpoints); + } else { + log.info("Found {} shell(s) for {} key(s)", collectedShells.size(), keys.size()); + return collectedShells; + } + + } finally { + watch.stop(); + log.info(TOOK_MS, watch.getLastTaskName(), watch.getLastTaskTimeMillis()); } } - @NotNull - private Stream fetchShellDescriptors(final Set calledEndpoints, - final String bpn, final List keys) { - log.info("Fetching {} shells for bpn {}", keys.size(), bpn); - final var connectorEndpoints = connectorEndpointsService.fetchConnectorEndpoints(bpn); - calledEndpoints.addAll(connectorEndpoints); + private Stream fetchShellDescriptors( + final Map.Entry> entry, final Set calledEndpoints) { - final List descriptors = new ArrayList<>(); + try { - EndpointDataReference endpointDataReference = null; + final var futures = fetchShellDescriptors(calledEndpoints, entry.getKey(), entry.getValue()); + final var shellDescriptors = futures.get(); + return shellDescriptors.stream(); - for (final DigitalTwinRegistryKey key : keys) { - endpointDataReference = renewIfNecessary(endpointDataReference, connectorEndpoints); - descriptors.add(fetchShellDescriptor(endpointDataReference, key)); + } catch (InterruptedException e) { + log.error(e.getMessage(), e); + Thread.currentThread().interrupt(); + return Stream.empty(); + } catch (ExecutionException e) { + log.warn(e.getMessage(), e); + return Stream.empty(); } - - return descriptors.stream(); } - private EndpointDataReference renewIfNecessary(final EndpointDataReference endpointDataReference, - final List connectorEndpoints) { - if (endpointDataReference == null || endpointDataReference.getAuthCode() == null) { - return getEndpointDataReference(connectorEndpoints); - } else { - final var tokenExpirationInstant = extractTokenExpiration(endpointDataReference.getAuthCode()); - if (Instant.now().isAfter(tokenExpirationInstant)) { - log.info("EndpointDataReference token has expired, getting a new one."); - return getEndpointDataReference(connectorEndpoints); - } - return endpointDataReference; + private CompletableFuture> fetchShellDescriptors( + final Set calledEndpoints, final String bpn, final List keys) { + + final var watch = new StopWatch(); + final String msg = "Fetching %s shells for bpn '%s'".formatted(keys.size(), bpn); + watch.start(msg); + log.info(msg); + + try { + final var connectorEndpoints = connectorEndpointsService.fetchConnectorEndpoints(bpn); + + log.info("Found {} connector endpoints for bpn '{}'", connectorEndpoints.size(), bpn); + calledEndpoints.addAll(connectorEndpoints); + + return fetchShellDescriptorsForConnectorEndpoints(keys, connectorEndpoints); + + } finally { + watch.stop(); + log.info(TOOK_MS, watch.getLastTaskName(), watch.getLastTaskTimeMillis()); } } - private Instant extractTokenExpiration(final String token) { - return Instant.ofEpochSecond(EDRAuthCode.fromAuthCodeToken(token).getExp()); + private CompletableFuture> fetchShellDescriptorsForConnectorEndpoints( + final List keys, final List connectorEndpoints) { + + final var service = endpointDataForConnectorsService; + final var futures = service.createFindEndpointDataForConnectorsFutures(connectorEndpoints) + .stream() + .map(edrFuture -> edrFuture.thenCompose(edr -> CompletableFuture.supplyAsync( + () -> fetchShellDescriptorsForKey(keys, edr)))) + .toList(); + + log.debug("Created {} futures", futures.size()); + + return resultFinder.getFastestResult(futures); + } + + private List fetchShellDescriptorsForKey( + final List keys, final EndpointDataReference endpointDataReference) { + + final var watch = new StopWatch(); + final String msg = "Fetching shell descriptors for keys %s from endpoint '%s'".formatted(keys, + endpointDataReference.getEndpoint()); + watch.start(msg); + log.info(msg); + try { + return keys.stream().map(key -> new Shell(contractNegotiationId(endpointDataReference.getAuthCode()), + fetchShellDescriptor(endpointDataReference, key))).toList(); + } finally { + watch.stop(); + log.info(TOOK_MS, watch.getLastTaskName(), watch.getLastTaskTimeMillis()); + } } private AssetAdministrationShellDescriptor fetchShellDescriptor(final EndpointDataReference endpointDataReference, final DigitalTwinRegistryKey key) { - log.info("Retrieving AAS Identification for DigitalTwinRegistryKey: {}", key); - final String aaShellIdentification = mapToShellId(endpointDataReference, key.shellId()); - return decentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor(endpointDataReference, - aaShellIdentification); + final var watch = new StopWatch(); + final String msg = "Retrieving AAS identification for DigitalTwinRegistryKey: '%s'".formatted(key); + watch.start(msg); + log.info(msg); + try { + final String aaShellIdentification = mapToShellId(endpointDataReference, key.shellId()); + return decentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor(endpointDataReference, + aaShellIdentification); + } finally { + watch.stop(); + log.info(TOOK_MS, watch.getLastTaskName(), watch.getLastTaskTimeMillis()); + } + } + + private String contractNegotiationId(final String token) { + return Optional.ofNullable(token) + .map(EDRAuthCode::fromAuthCodeToken) + .map(EDRAuthCode::getCid) + .orElse(""); } /** @@ -134,37 +229,114 @@ private AssetAdministrationShellDescriptor fetchShellDescriptor(final EndpointDa */ @NotNull private String mapToShellId(final EndpointDataReference endpointDataReference, final String key) { - final var identifierKeyValuePair = IdentifierKeyValuePair.builder().name("globalAssetId").value(key).build(); - final var aaShellIdentification = decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink( - endpointDataReference, List.of(identifierKeyValuePair)).getResult().stream().findFirst().orElse(key); - - if (key.equals(aaShellIdentification)) { - log.info("Found shell with shellId {} in registry", aaShellIdentification); - } else { - log.info("Retrieved shellId {} for globalAssetId {}", aaShellIdentification, key); + + final var watch = new StopWatch(); + final String msg = "Mapping '%s' to shell ID for endpoint '%s'".formatted(key, + endpointDataReference.getEndpoint()); + watch.start(msg); + log.info(msg); + + try { + + final var identifierKeyValuePair = IdentifierKeyValuePair.builder() + .name("globalAssetId") + .value(key) + .build(); + final var aaShellIdentification = decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink( + endpointDataReference, List.of(identifierKeyValuePair)) + .getResult() + .stream() + .findFirst() + .orElse(key); + + if (key.equals(aaShellIdentification)) { + log.info("Found shell with shellId {} in registry", aaShellIdentification); + } else { + log.info("Retrieved shellId {} for globalAssetId {}", aaShellIdentification, key); + } + + return aaShellIdentification; + + } finally { + watch.stop(); + log.info(TOOK_MS, watch.getLastTaskName(), watch.getLastTaskTimeMillis()); + } + } + + @SuppressWarnings("PMD.AvoidCatchingGenericException") + private Collection lookupShellIds(final String bpn) throws RegistryServiceException { + + log.info("Looking up shell ids for bpn {}", bpn); + + try { + + final var connectorEndpoints = connectorEndpointsService.fetchConnectorEndpoints(bpn); + log.info("Looking up shell ids for bpn '{}' with connector endpoints {}", bpn, connectorEndpoints); + + final var endpointDataReferenceFutures = endpointDataForConnectorsService.createFindEndpointDataForConnectorsFutures( + connectorEndpoints); + log.debug("Created endpointDataReferenceFutures"); + + return lookupShellIds(bpn, endpointDataReferenceFutures); + + } catch (RuntimeException e) { + // catching generic exception is intended here, + // otherwise Jobs stay in state RUNNING forever + log.error(e.getMessage(), e); + throw new RegistryServiceException( + "%s occurred while looking up shell ids for bpn '%s'".formatted(e.getClass().getSimpleName(), bpn), + e); } - return aaShellIdentification; } @NotNull - private EndpointDataReference getEndpointDataReference(final List connectorEndpoints) { - return endpointDataForConnectorsService.findEndpointDataForConnectors(connectorEndpoints); + private Collection lookupShellIds(final String bpn, + final List> endpointDataReferenceFutures) + throws RegistryServiceException { + + try { + final var futures = endpointDataReferenceFutures.stream() + .map(edrFuture -> edrFuture.thenCompose( + edr -> CompletableFuture.supplyAsync( + () -> lookupShellIds(bpn, edr)))) + .toList(); + final var shellIds = resultFinder.getFastestResult(futures).get(); + + log.info("Found {} shell id(s) in total", shellIds.size()); + return shellIds; + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RegistryServiceException( + "%s occurred while looking up shell ids for bpn '%s'".formatted(e.getClass().getSimpleName(), bpn), + e); + } catch (ExecutionException e) { + throw new RegistryServiceException( + "%s occurred while looking up shell ids for bpn '%s'".formatted(e.getClass().getSimpleName(), bpn), + e); + } } - private Collection lookupShellIds(final String bpn) { - log.info("Looking up shell ids for bpn {}", bpn); - final var connectorEndpoints = connectorEndpointsService.fetchConnectorEndpoints(bpn); - final var endpointDataReference = getEndpointDataReference(connectorEndpoints); - - final var shellIds = decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink( - endpointDataReference, - List.of(IdentifierKeyValuePair.builder().name("manufacturerId").value(bpn).build())).getResult(); - log.info("Found {} shell id(s) in total", shellIds.size()); - return shellIds; + private Collection lookupShellIds(final String bpn, final EndpointDataReference endpointDataReference) { + + final var watch = new StopWatch(); + final String msg = "Looking up shell IDs for bpn '%s' with endpointDataReference '%s'".formatted(bpn, + endpointDataReference); + watch.start(msg); + log.info(msg); + + try { + return decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink( + endpointDataReference, + List.of(IdentifierKeyValuePair.builder().name("manufacturerId").value(bpn).build())).getResult(); + } finally { + watch.stop(); + log.info(TOOK_MS, watch.getLastTaskName(), watch.getLastTaskTimeMillis()); + } } @Override - public Collection lookupShellIdentifiers(final String bpn) { + public Collection lookupShellIdentifiers(final String bpn) throws RegistryServiceException { return lookupShellIds(bpn).stream().map(id -> new DigitalTwinRegistryKey(id, bpn)).toList(); } diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsService.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsService.java index e54e6ebfb1..f6442bdaf2 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsService.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsService.java @@ -23,12 +23,17 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.registryclient.decentral; +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.springframework.web.client.RestClientException; +import org.springframework.util.StopWatch; /** * Service that use edc client to make calls to edc connector endpoints @@ -40,22 +45,51 @@ public class EndpointDataForConnectorsService { private static final String DT_REGISTRY_ASSET_TYPE = "https://w3id.org/edc/v0.0.1/ns/type"; private static final String DT_REGISTRY_ASSET_VALUE = "data.core.digitalTwinRegistry"; + private static final String TOOK_MS = "{} took {} ms"; private final EdcEndpointReferenceRetriever edcSubmodelFacade; - public EndpointDataReference findEndpointDataForConnectors(final List connectorEndpoints) { - for (final String connector : connectorEndpoints) { - log.info("Trying to retrieve EndpointDataReference for connector {}", connector); - try { - return edcSubmodelFacade.getEndpointReferenceForAsset(connector, DT_REGISTRY_ASSET_TYPE, - DT_REGISTRY_ASSET_VALUE); - } catch (EdcRetrieverException e) { - log.warn("Exception occurred when retrieving EndpointDataReference from connector {}", connector, e); - } + public List> createFindEndpointDataForConnectorsFutures( + final List connectorEndpoints) { + + final var watch = new StopWatch(); + final String msg = "Creating futures to get EndpointDataReferences for endpoints: %s".formatted( + connectorEndpoints); + watch.start(msg); + log.info(msg); + + List> futures = Collections.emptyList(); + try { + futures = connectorEndpoints.stream() + .map(connectorEndpoint -> supplyAsync( + () -> getEndpointReferenceForAsset(connectorEndpoint))) + .toList(); + return futures; + } finally { + log.info("Created {} futures", futures.size()); + watch.stop(); + log.info(TOOK_MS, watch.getLastTaskName(), watch.getLastTaskTimeMillis()); } - throw new RestClientException( - "EndpointDataReference was not found. Requested connectorEndpoints: " + String.join(", ", - connectorEndpoints)); + } + + private EndpointDataReference getEndpointReferenceForAsset(final String connector) { + + final var watch = new StopWatch(); + final String msg = "Trying to retrieve EndpointDataReference for connector '%s'".formatted(connector); + watch.start(msg); + log.info(msg); + + try { + return edcSubmodelFacade.getEndpointReferenceForAsset(connector, DT_REGISTRY_ASSET_TYPE, + DT_REGISTRY_ASSET_VALUE); + } catch (EdcRetrieverException e) { + log.warn("Exception occurred when retrieving EndpointDataReference from connector '{}'", connector, e); + throw new CompletionException(e.getMessage(), e); + } finally { + watch.stop(); + log.info(TOOK_MS, watch.getLastTaskName(), watch.getLastTaskTimeMillis()); + } + } } diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/discovery/ConnectorEndpointsService.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/discovery/ConnectorEndpointsService.java index 511156f104..590eb66a5b 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/discovery/ConnectorEndpointsService.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/discovery/ConnectorEndpointsService.java @@ -46,26 +46,27 @@ public class ConnectorEndpointsService { @Cacheable(CONNECTOR_ENDPOINT_SERVICE_CACHE_NAME) public List fetchConnectorEndpoints(final String bpn) { + if (StringUtils.isBlank(bpn)) { log.warn("BPN was null, cannot search for any connector endpoints. Returning empty list."); return List.of(); } log.info("Requesting connector endpoints for BPN {}", bpn); - final DiscoveryFinderRequest onlyBpn = new DiscoveryFinderRequest(List.of("bpn")); - final List discoveryEndpoints = discoveryFinderClient.findDiscoveryEndpoints(onlyBpn) - .endpoints(); - final List providedBpn = List.of(bpn); + + final var onlyBpn = new DiscoveryFinderRequest(List.of("bpn")); + final var discoveryEndpoints = discoveryFinderClient.findDiscoveryEndpoints(onlyBpn).endpoints(); final var endpoints = discoveryEndpoints.stream() .flatMap( discoveryEndpoint -> discoveryFinderClient.findConnectorEndpoints( - discoveryEndpoint.endpointAddress(), providedBpn) + discoveryEndpoint.endpointAddress(), List.of(bpn)) .stream() .filter(edcDiscoveryResult -> edcDiscoveryResult.bpn() .equals(bpn)) .map(EdcDiscoveryResult::connectorEndpoint)) .flatMap(List::stream) .toList(); + log.info("Discovered the following endpoints for BPN '{}': '{}'", bpn, String.join(", ", endpoints)); return endpoints; } diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/discovery/DiscoveryFinderClientImpl.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/discovery/DiscoveryFinderClientImpl.java index dd47d4a513..b2d375759b 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/discovery/DiscoveryFinderClientImpl.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/discovery/DiscoveryFinderClientImpl.java @@ -65,9 +65,11 @@ public void evictDiscoveryEndpointsCacheValues() { @Override @Retry(name = "registry") public List findConnectorEndpoints(final String endpointAddress, final List bpns) { - final EdcDiscoveryResult[] edcDiscoveryResults = restTemplate.postForObject(endpointAddress, bpns, - EdcDiscoveryResult[].class); - return edcDiscoveryResults == null ? List.of() : List.of(edcDiscoveryResults); + return toList(restTemplate.postForObject(endpointAddress, bpns, EdcDiscoveryResult[].class)); + } + + private static List toList(final T... arr) { + return arr == null ? List.of() : List.of(arr); } } diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/exceptions/RegistryServiceException.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/exceptions/RegistryServiceException.java index 1aebfdad34..3995b27a30 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/exceptions/RegistryServiceException.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/exceptions/RegistryServiceException.java @@ -31,4 +31,8 @@ public class RegistryServiceException extends Exception { public RegistryServiceException(final String msg) { super(msg); } + + public RegistryServiceException(final String msg, final Throwable cause) { + super(msg, cause); + } } diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/DefaultConfigurationTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/DefaultConfigurationTest.java index 0372bb3ef5..4a3b67fd5f 100644 --- a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/DefaultConfigurationTest.java +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/DefaultConfigurationTest.java @@ -26,17 +26,20 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.List; +import java.util.concurrent.ExecutionException; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelClient; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelFacade; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.registryclient.decentral.EdcRetrieverException; import org.junit.jupiter.api.Test; -import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; class DefaultConfigurationTest { @@ -74,12 +77,27 @@ void edcSubmodelFacade() { @Test void endpointDataForConnectorsService() throws EdcClientException { + + // ARRANGE final var mock = mock(EdcSubmodelFacade.class); + final var endpointAddress = "endpointaddress"; + final var endpointDataReference = EndpointDataReference.Builder.newInstance().endpoint(endpointAddress).build(); + when(mock.getEndpointReferenceForAsset(eq(endpointAddress), any(), any())).thenReturn(endpointDataReference); + // ACT final var endpointDataForConnectorsService = testee.endpointDataForConnectorsService(mock); - endpointDataForConnectorsService.findEndpointDataForConnectors(List.of("test")); - verify(mock).getEndpointReferenceForAsset(any(), any(), any()); + endpointDataForConnectorsService.createFindEndpointDataForConnectorsFutures(List.of(endpointAddress)) // + .forEach(future -> { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }); + + // ASSERT + verify(mock).getEndpointReferenceForAsset(eq(endpointAddress), any(), any()); } @Test @@ -89,9 +107,10 @@ void endpointDataForConnectorsService_withException() throws EdcClientException final var endpointDataForConnectorsService = testee.endpointDataForConnectorsService(mock); final var dummyEndpoints = List.of("test"); - assertThatThrownBy( - () -> endpointDataForConnectorsService.findEndpointDataForConnectors(dummyEndpoints)).isInstanceOf( - RestClientException.class); - + endpointDataForConnectorsService.createFindEndpointDataForConnectorsFutures(dummyEndpoints).forEach(future -> { + assertThatThrownBy(future::get).isInstanceOf(ExecutionException.class) + .extracting(Throwable::getCause) + .isInstanceOf(EdcRetrieverException.class); + }); } } \ No newline at end of file diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryServiceTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryServiceTest.java index 0e4e9d1410..0d88ade1b3 100644 --- a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryServiceTest.java +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryServiceTest.java @@ -37,6 +37,7 @@ import java.util.Collections; import java.util.List; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; import org.eclipse.tractusx.irs.component.assetadministrationshell.Endpoint; import org.eclipse.tractusx.irs.component.assetadministrationshell.SubmodelDescriptor; @@ -78,11 +79,12 @@ void setUp() throws IOException { void shouldReturnSubmodelEndpointsWhenRequestingWithCatenaXId() throws RegistryServiceException { final String existingCatenaXId = "urn:uuid:a65c35a8-8d31-4a86-899b-57912de33675"; - final Collection aasShellDescriptor = digitalTwinRegistryService.fetchShells( + final Collection aasShellDescriptor = digitalTwinRegistryService.fetchShells( List.of(new DigitalTwinRegistryKey(existingCatenaXId, ""))); final List shellEndpoints = aasShellDescriptor.stream() .findFirst() .get() + .payload() .getSubmodelDescriptors(); assertThat(shellEndpoints).isNotNull().isNotEmpty(); @@ -119,7 +121,7 @@ void shouldReturnTombstoneWhenClientReturnsEmptyDescriptor() { LookupShellsResponse.builder().result(Collections.emptyList()).build()); final List submodelEndpoints = dtRegistryFacadeWithMock.fetchShells( - List.of(new DigitalTwinRegistryKey(catenaXId, ""))).stream().findFirst().get().getSubmodelDescriptors(); + List.of(new DigitalTwinRegistryKey(catenaXId, ""))).stream().findFirst().get().payload().getSubmodelDescriptors(); assertThat(submodelEndpoints).isEmpty(); } @@ -155,6 +157,7 @@ void shouldReturnAssetAdministrationShellDescriptorForFoundIdentification() { .stream() .findFirst() .get() + .payload() .getSubmodelDescriptors(); assertThat(submodelEndpoints).isEmpty(); } @@ -178,6 +181,7 @@ void shouldReturnSubmodelEndpointsWhenFilteringByAspectType() throws RegistrySer .stream() .findFirst() .get() + .payload() .getSubmodelDescriptors(); assertThat(shellEndpoints).isNotNull().isNotEmpty(); diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceTest.java index 5df9a62db4..926b9de649 100644 --- a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceTest.java +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceTest.java @@ -23,192 +23,253 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.registryclient.decentral; +import static java.util.Collections.emptyList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.function.Function; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; -import org.assertj.core.api.Assertions; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.irs.common.util.concurrent.ResultFinder; +import org.eclipse.tractusx.irs.component.Shell; import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; import org.eclipse.tractusx.irs.component.assetadministrationshell.IdentifierKeyValuePair; import org.eclipse.tractusx.irs.component.assetadministrationshell.SubmodelDescriptor; -import org.eclipse.tractusx.irs.data.StringMapper; -import org.eclipse.tractusx.irs.edc.client.model.EDRAuthCode; import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryKey; import org.eclipse.tractusx.irs.registryclient.discovery.ConnectorEndpointsService; import org.eclipse.tractusx.irs.registryclient.exceptions.RegistryServiceException; +import org.eclipse.tractusx.irs.registryclient.exceptions.ShellNotFoundException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class DecentralDigitalTwinRegistryServiceTest { - private final ConnectorEndpointsService connectorEndpointsService = Mockito.mock(ConnectorEndpointsService.class); - private final EndpointDataForConnectorsService endpointDataForConnectorsService = Mockito.mock( + private final ConnectorEndpointsService connectorEndpointsService = mock(ConnectorEndpointsService.class); + private final EndpointDataForConnectorsService endpointDataForConnectorsService = mock( EndpointDataForConnectorsService.class); - private final DecentralDigitalTwinRegistryClient decentralDigitalTwinRegistryClient = Mockito.mock( + private final DecentralDigitalTwinRegistryClient decentralDigitalTwinRegistryClient = mock( DecentralDigitalTwinRegistryClient.class); - private final DecentralDigitalTwinRegistryService decentralDigitalTwinRegistryService = new DecentralDigitalTwinRegistryService( + private final DecentralDigitalTwinRegistryService sut = new DecentralDigitalTwinRegistryService( connectorEndpointsService, endpointDataForConnectorsService, decentralDigitalTwinRegistryClient); - private static String createAuthCode(final Function expirationModifier) { - final var serializedEdrAuthCode = StringMapper.mapToString( - EDRAuthCode.builder().exp(expirationModifier.apply(Instant.now()).getEpochSecond()).build()); - final var bytes = serializedEdrAuthCode.getBytes(StandardCharsets.UTF_8); - return Base64.getUrlEncoder().encodeToString(bytes); - } - public static AssetAdministrationShellDescriptor shellDescriptor( final List submodelDescriptors) { + + final var specificAssetIds = List.of( + IdentifierKeyValuePair.builder().name("ManufacturerId").value("BPNL00000003AYRE").build()); + return AssetAdministrationShellDescriptor.builder() - .specificAssetIds(List.of(IdentifierKeyValuePair.builder() - .name("ManufacturerId") - .value("BPNL00000003AYRE") - .build())) + .specificAssetIds(specificAssetIds) .submodelDescriptors(submodelDescriptors) .build(); } - @Test - void shouldReturnExpectedShell() throws RegistryServiceException { - // given - final DigitalTwinRegistryKey digitalTwinRegistryKey = new DigitalTwinRegistryKey( - "urn:uuid:4132cd2b-cbe7-4881-a6b4-39fdc31cca2b", "bpn"); - final AssetAdministrationShellDescriptor expectedShell = shellDescriptor(Collections.emptyList()); - EndpointDataReference endpointDataReference = EndpointDataReference.Builder.newInstance() - .endpoint("url.to.host") - .build(); - final LookupShellsResponse lookupShellsResponse = LookupShellsResponse.builder() - .result(Collections.emptyList()) - .build(); - when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("address")); - when(endpointDataForConnectorsService.findEndpointDataForConnectors(ArgumentMatchers.anyList())).thenReturn( - endpointDataReference); - when(decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink(any(), - ArgumentMatchers.anyList())).thenReturn(lookupShellsResponse); - when(decentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor(any(), any())).thenReturn( - expectedShell); - - // when - final Collection actualShell = decentralDigitalTwinRegistryService.fetchShells( - List.of(digitalTwinRegistryKey)); - - // then - Assertions.assertThat(actualShell).containsExactly(expectedShell); + @Nested + @DisplayName("fetchShells") + class FetchShellsTests { + + @Test + void shouldReturnExpectedShell() throws RegistryServiceException { + // given + final var digitalTwinRegistryKey = new DigitalTwinRegistryKey( + "urn:uuid:4132cd2b-cbe7-4881-a6b4-39fdc31cca2b", "bpn"); + final var expectedShell = shellDescriptor(emptyList()); + final var endpointDataReference = endpointDataReference("url.to.host"); + final var lookupShellsResponse = LookupShellsResponse.builder().result(emptyList()).build(); + + when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("address")); + + final var endpointDataRefFutures = List.of(completedFuture(endpointDataReference)); + when(endpointDataForConnectorsService.createFindEndpointDataForConnectorsFutures(anyList())).thenReturn( + endpointDataRefFutures); + + when(decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink(any(), + anyList())).thenReturn(lookupShellsResponse); + when(decentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor(any(), any())).thenReturn( + expectedShell); + + // when + final var actualShell = sut.fetchShells(List.of(digitalTwinRegistryKey)).stream().map(Shell::payload); + + // then + assertThat(actualShell).containsExactly(expectedShell); + } + + @Test + void whenInterruptedExceptionOccurs() throws ExecutionException, InterruptedException { + + // given + simulateResultFinderInterrupted(); + + final var lookupShellsResponse = LookupShellsResponse.builder().result(emptyList()).build(); + + final List connectorEndpoints = List.of("address1", "address2"); + when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(connectorEndpoints); + + final var dataRefFutures = List.of( // + completedFuture(endpointDataReference("url.to.host1")), // + completedFuture(endpointDataReference("url.to.host2"))); + when(endpointDataForConnectorsService.createFindEndpointDataForConnectorsFutures( + connectorEndpoints)).thenReturn(dataRefFutures); + + when(decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink(any(), + anyList())).thenReturn(lookupShellsResponse); + when(decentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor(any(), any())).thenReturn( + shellDescriptor(emptyList())); + + // when + final ThrowingCallable call = () -> sut.fetchShells( + List.of(new DigitalTwinRegistryKey("dummyShellId", "dummyBpn"))); + + // then + assertThatThrownBy(call).isInstanceOf(ShellNotFoundException.class) + .hasMessage("Unable to find any of the requested shells") + .satisfies(e -> assertThat( + ((ShellNotFoundException) e).getCalledEndpoints()).containsExactlyInAnyOrder( + "address1", "address2")); + } + + @Test + void whenExecutionExceptionOccurs() { + + // given + simulateGetFastestResultFailedFuture(); + + final var lookupShellsResponse = LookupShellsResponse.builder().result(emptyList()).build(); + + final List connectorEndpoints = List.of("address"); + when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(connectorEndpoints); + + final var dataRefFutures = List.of(completedFuture(endpointDataReference("url.to.host"))); + when(endpointDataForConnectorsService.createFindEndpointDataForConnectorsFutures( + connectorEndpoints)).thenReturn(dataRefFutures); + + when(decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink(any(), + anyList())).thenReturn(lookupShellsResponse); + when(decentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor(any(), any())).thenReturn( + shellDescriptor(emptyList())); + + // when + final var bpn = "dummyBpn"; + final ThrowingCallable call = () -> sut.fetchShells( + List.of(new DigitalTwinRegistryKey("dummyShellId", bpn))); + + // then + assertThatThrownBy(call).isInstanceOf(ShellNotFoundException.class) + .hasMessageContaining("Unable to find any of the requested shells"); + + } + + @Test + void shouldThrowShellNotFoundException_ifNoDigitalTwinRegistryKeysGiven() { + assertThatThrownBy(() -> sut.fetchShells(emptyList())).isInstanceOf(ShellNotFoundException.class); + } + + } + + private void simulateGetFastestResultFailedFuture() { + final ResultFinder resultFinderMock = mock(ResultFinder.class); + when(resultFinderMock.getFastestResult(any())).thenReturn( + CompletableFuture.failedFuture(new IllegalStateException("some illegal state"))); + sut.setResultFinder(resultFinderMock); + } + + private void simulateResultFinderInterrupted() throws InterruptedException, ExecutionException { + final ResultFinder resultFinderMock = mock(ResultFinder.class); + final CompletableFuture completableFutureMock = mock(CompletableFuture.class); + when(completableFutureMock.get()).thenThrow(new InterruptedException("interrupted")); + when(resultFinderMock.getFastestResult(any())).thenReturn(completableFutureMock); + sut.setResultFinder(resultFinderMock); } - @Test - void shouldRenewEndpointDataReferenceForMultipleAssets() throws RegistryServiceException { - // given - final DigitalTwinRegistryKey digitalTwinRegistryKey = new DigitalTwinRegistryKey( - "urn:uuid:4132cd2b-cbe7-4881-a6b4-39fdc31cca2b", "bpn"); - final AssetAdministrationShellDescriptor expectedShell = shellDescriptor(Collections.emptyList()); - final var authCode = "test." + createAuthCode(exp -> exp.minus(1, ChronoUnit.DAYS)); - EndpointDataReference endpointDataReference = EndpointDataReference.Builder.newInstance() - .endpoint("url.to.host") - .authKey("test") - .authCode(authCode) - .build(); - EndpointDataReference renewedReference = EndpointDataReference.Builder.newInstance() - .endpoint("url.to.host") - .build(); - final LookupShellsResponse lookupShellsResponse = LookupShellsResponse.builder() - .result(Collections.emptyList()) - .build(); - when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("address")); - when(endpointDataForConnectorsService.findEndpointDataForConnectors(ArgumentMatchers.anyList())).thenReturn( - endpointDataReference, renewedReference); - when(decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink(any(), - ArgumentMatchers.anyList())).thenReturn(lookupShellsResponse); - when(decentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor(any(), any())).thenReturn( - expectedShell); - - // when - final Collection actualShell = decentralDigitalTwinRegistryService.fetchShells( - List.of(digitalTwinRegistryKey, digitalTwinRegistryKey)); - - // then - Assertions.assertThat(actualShell).containsExactly(expectedShell, expectedShell); - - verify(endpointDataForConnectorsService, times(2)).findEndpointDataForConnectors(anyList()); + private static EndpointDataReference endpointDataReference(final String url) { + return endpointDataReferenceBuilder().endpoint(url).build(); } - @Test - void shouldNotRenewEndpointDataReferenceForMultipleAssets() throws RegistryServiceException { - // given - final DigitalTwinRegistryKey digitalTwinRegistryKey = new DigitalTwinRegistryKey( - "urn:uuid:4132cd2b-cbe7-4881-a6b4-39fdc31cca2b", "bpn"); - final AssetAdministrationShellDescriptor expectedShell = shellDescriptor(Collections.emptyList()); - final var authCode = "test." + createAuthCode(exp -> exp.plus(1, ChronoUnit.DAYS)); - EndpointDataReference endpointDataReference = EndpointDataReference.Builder.newInstance() - .endpoint("url.to.host") - .authKey("test") - .authCode(authCode) - .build(); - final LookupShellsResponse lookupShellsResponse = LookupShellsResponse.builder() - .result(Collections.emptyList()) - .build(); - when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("address")); - when(endpointDataForConnectorsService.findEndpointDataForConnectors(ArgumentMatchers.anyList())).thenReturn( - endpointDataReference); - when(decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink(any(), - ArgumentMatchers.anyList())).thenReturn(lookupShellsResponse); - when(decentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor(any(), any())).thenReturn( - expectedShell); - - // when - final Collection actualShell = decentralDigitalTwinRegistryService.fetchShells( - List.of(digitalTwinRegistryKey, digitalTwinRegistryKey, digitalTwinRegistryKey)); - - // then - Assertions.assertThat(actualShell).containsExactly(expectedShell, expectedShell, expectedShell); - - verify(endpointDataForConnectorsService, times(1)).findEndpointDataForConnectors(anyList()); + @Nested + @DisplayName("lookupGlobalAssetIds") + class LookupGlobalAssetIdsTests { + + @Test + void shouldReturnTheExpectedGlobalAssetId() throws RegistryServiceException { + // given + final var digitalTwinRegistryKey = new DigitalTwinRegistryKey( + "urn:uuid:4132cd2b-cbe7-4881-a6b4-39fdc31cca2b", "bpn"); + + final var expectedGlobalAssetId = "urn:uuid:4132cd2b-cbe7-4881-a6b4-aaaaaaaaaaaa"; + final var expectedShell = shellDescriptor(emptyList()).toBuilder() + .globalAssetId(expectedGlobalAssetId) + .build(); + final var dataRefFutures = List.of(completedFuture(endpointDataReference("url.to.host"))); + final var lookupShellsResponse = LookupShellsResponse.builder() + .result(List.of(digitalTwinRegistryKey.shellId())) + .build(); + when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("address")); + when(endpointDataForConnectorsService.createFindEndpointDataForConnectorsFutures(anyList())).thenReturn( + dataRefFutures); + when(decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink(any(), + anyList())).thenReturn(lookupShellsResponse); + when(decentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor(any(), any())).thenReturn( + expectedShell); + + // when + final var assetAdministrationShellDescriptors = sut.lookupShellsByBPN(digitalTwinRegistryKey.bpn()); + + String actualGlobalAssetId = assetAdministrationShellDescriptors.stream() + .findFirst() + .map(Shell::payload) + .map(AssetAdministrationShellDescriptor::getGlobalAssetId) + .get();// then + assertThat(actualGlobalAssetId).isEqualTo(expectedGlobalAssetId); + } + + @Test + void whenInterruptedExceptionOccurs() throws ExecutionException, InterruptedException { + // given + simulateResultFinderInterrupted(); + + // when + final ThrowingCallable call = () -> sut.lookupShellsByBPN("dummyBpn"); + + // then + assertThatThrownBy(call).isInstanceOf(RegistryServiceException.class) + .hasMessageContaining( + "InterruptedException occurred while looking up shell ids for bpn") + .hasMessageContaining("dummyBpn"); + } + + @Test + void whenExecutionExceptionOccurs() { + // given + simulateGetFastestResultFailedFuture(); + + // when + final var bpn = "dummyBpn"; + final ThrowingCallable call = () -> sut.lookupShellsByBPN(bpn); + + // then + assertThatThrownBy(call).isInstanceOf(RegistryServiceException.class) + .hasMessageContaining("Exception occurred while looking up shell ids for bpn") + .hasMessageContaining("'" + bpn + "'"); + } } - @Test - void shouldReturnExpectedGlobalAssetId() throws RegistryServiceException { - // given - final DigitalTwinRegistryKey digitalTwinRegistryKey = new DigitalTwinRegistryKey( - "urn:uuid:4132cd2b-cbe7-4881-a6b4-39fdc31cca2b", "bpn"); - - final String expectedGlobalAssetId = "urn:uuid:4132cd2b-cbe7-4881-a6b4-aaaaaaaaaaaa"; - final var expectedShell = shellDescriptor(Collections.emptyList()).toBuilder() - .globalAssetId(expectedGlobalAssetId) - .build(); - final var endpointDataReference = EndpointDataReference.Builder.newInstance().endpoint("url.to.host").build(); - final LookupShellsResponse lookupShellsResponse = LookupShellsResponse.builder() - .result(List.of( - digitalTwinRegistryKey.shellId())) - .build(); - when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("address")); - when(endpointDataForConnectorsService.findEndpointDataForConnectors(ArgumentMatchers.anyList())).thenReturn( - endpointDataReference); - when(decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink(any(), - ArgumentMatchers.anyList())).thenReturn(lookupShellsResponse); - when(decentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor(any(), any())).thenReturn( - expectedShell); - - // when - final Collection assetAdministrationShellDescriptors = decentralDigitalTwinRegistryService.lookupShellsByBPN( - digitalTwinRegistryKey.bpn()); - - String actualGlobalAssetId = assetAdministrationShellDescriptors.stream().findFirst().map(AssetAdministrationShellDescriptor::getGlobalAssetId).get(); - // then - Assertions.assertThat(actualGlobalAssetId).isEqualTo(expectedGlobalAssetId); + private static EndpointDataReference.Builder endpointDataReferenceBuilder() { + return EndpointDataReference.Builder.newInstance(); } -} +} \ No newline at end of file diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceWiremockTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceWiremockTest.java new file mode 100644 index 0000000000..36ce0ff1b6 --- /dev/null +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceWiremockTest.java @@ -0,0 +1,217 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.registryclient.decentral; + +import static com.github.tomakehurst.wiremock.client.WireMock.exactly; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.givenThat; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_URL; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.EDC_DISCOVERY_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.TEST_BPN; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postDiscoveryFinder200; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postDiscoveryFinder404; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postEdcDiscovery200; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postEdcDiscovery404; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.DATAPLANE_URL; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.LOOKUP_SHELLS_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.LOOKUP_SHELLS_TEMPLATE; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.SHELL_DESCRIPTORS_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.SHELL_DESCRIPTORS_TEMPLATE; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getLookupShells200; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getLookupShells200Empty; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getLookupShells404; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getShellDescriptor200; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getShellDescriptor404; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.irs.component.Shell; +import org.eclipse.tractusx.irs.data.StringMapper; +import org.eclipse.tractusx.irs.edc.client.configuration.JsonLdConfiguration; +import org.eclipse.tractusx.irs.edc.client.model.EDRAuthCode; +import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryKey; +import org.eclipse.tractusx.irs.registryclient.discovery.ConnectorEndpointsService; +import org.eclipse.tractusx.irs.registryclient.discovery.DiscoveryFinderClientImpl; +import org.eclipse.tractusx.irs.registryclient.exceptions.RegistryServiceException; +import org.eclipse.tractusx.irs.registryclient.exceptions.ShellNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.client.RestTemplate; + +@WireMockTest +class DecentralDigitalTwinRegistryServiceWiremockTest { + private static final String PROXY_SERVER_HOST = "127.0.0.1"; + private final EdcEndpointReferenceRetriever edcSubmodelFacadeMock = mock(EdcEndpointReferenceRetriever.class); + private DecentralDigitalTwinRegistryService decentralDigitalTwinRegistryService; + + @BeforeEach + void setUp(WireMockRuntimeInfo wireMockRuntimeInfo) throws EdcRetrieverException { + final RestTemplate restTemplate = restTemplateProxy(PROXY_SERVER_HOST, wireMockRuntimeInfo.getHttpPort()); + + final var discoveryFinderClient = new DiscoveryFinderClientImpl(DISCOVERY_FINDER_URL, restTemplate); + final var connectorEndpointsService = new ConnectorEndpointsService(discoveryFinderClient); + final var endpointDataForConnectorsService = new EndpointDataForConnectorsService(edcSubmodelFacadeMock); + final var decentralDigitalTwinRegistryClient = new DecentralDigitalTwinRegistryClient(restTemplate, + SHELL_DESCRIPTORS_TEMPLATE, LOOKUP_SHELLS_TEMPLATE); + decentralDigitalTwinRegistryService = new DecentralDigitalTwinRegistryService(connectorEndpointsService, + endpointDataForConnectorsService, decentralDigitalTwinRegistryClient); + final var endpointDataReference = endpointDataReference("assetId"); + when(edcSubmodelFacadeMock.getEndpointReferenceForAsset(any(), any(), any())).thenReturn(endpointDataReference); + } + + @Test + void shouldDiscoverEDCAndRequestRegistry() throws RegistryServiceException { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + givenThat(getLookupShells200()); + givenThat(getShellDescriptor200()); + + // Act + final Collection shells = decentralDigitalTwinRegistryService.fetchShells( + List.of(new DigitalTwinRegistryKey("testId", TEST_BPN))); + + // Assert + assertThat(shells).hasSize(1); + assertThat(shells.stream().findFirst().get().payload().getSubmodelDescriptors()).hasSize(3); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + verify(exactly(1), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + verify(exactly(1), getRequestedFor(urlPathMatching(SHELL_DESCRIPTORS_PATH + ".*"))); + } + + @Test + void shouldThrowInCaseOfDiscoveryError() { + // Arrange + givenThat(postDiscoveryFinder404()); + final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + + // Act & Assert + assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( + ShellNotFoundException.class); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + } + + @Test + void shouldThrowInCaseOfEdcDiscoveryError() { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery404()); + final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + + // Act & Assert + assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( + ShellNotFoundException.class); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + } + + @Test + void shouldThrowInCaseOfLookupShellsError() { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + givenThat(getLookupShells404()); + final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + + // Act & Assert + assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( + ShellNotFoundException.class); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + verify(exactly(1), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + } + + @Test + void shouldThrowInCaseOfShellDescriptorsError() { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + givenThat(getLookupShells200()); + givenThat(getShellDescriptor404()); + final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + + // Act & Assert + assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( + ShellNotFoundException.class); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + verify(exactly(1), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + verify(exactly(1), getRequestedFor(urlPathMatching(SHELL_DESCRIPTORS_PATH + ".*"))); + } + + @Test + void shouldThrowExceptionOnEmptyShells() { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + givenThat(getLookupShells200Empty()); + givenThat(getShellDescriptor404()); + final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + + // Act & Assert + assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( + ShellNotFoundException.class); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + verify(exactly(1), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + verify(exactly(1), getRequestedFor(urlPathMatching(SHELL_DESCRIPTORS_PATH + ".*"))); + } + + private EndpointDataReference endpointDataReference(final String contractAgreementId) { + return EndpointDataReference.Builder.newInstance() + .authKey("X-API-KEY") + .authCode(edrAuthCode(contractAgreementId)) + .properties( + Map.of(JsonLdConfiguration.NAMESPACE_EDC_CID, contractAgreementId)) + .endpoint(DATAPLANE_URL) + .build(); + } + + private String edrAuthCode(final String contractAgreementId) { + final EDRAuthCode edrAuthCode = EDRAuthCode.builder() + .cid(contractAgreementId) + .dad("test") + .exp(9999999999L) + .build(); + final String b64EncodedAuthCode = Base64.getUrlEncoder() + .encodeToString(StringMapper.mapToString(edrAuthCode) + .getBytes(StandardCharsets.UTF_8)); + return "eyJhbGciOiJSUzI1NiJ9." + b64EncodedAuthCode + ".test"; + } +} diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsServiceTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsServiceTest.java index 288aaa67f0..085b015d8b 100644 --- a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsServiceTest.java +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsServiceTest.java @@ -24,19 +24,21 @@ package org.eclipse.tractusx.irs.registryclient.decentral; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; import org.junit.jupiter.api.Test; -import org.springframework.web.client.RestClientException; class EndpointDataForConnectorsServiceTest { @@ -46,58 +48,100 @@ class EndpointDataForConnectorsServiceTest { private static final String connectionOneAddress = "connectionOneAddress"; private static final String connectionTwoAddress = "connectionTwoAddress"; + private static final EndpointDataReference CONNECTION_ONE_DATA_REF = // + EndpointDataReference.Builder.newInstance().endpoint(connectionOneAddress).build(); + + private static final EndpointDataReference CONNECTION_TWO_DATA_REF = // + EndpointDataReference.Builder.newInstance().endpoint(connectionTwoAddress).build(); + private final EdcEndpointReferenceRetriever edcSubmodelFacade = mock(EdcEndpointReferenceRetriever.class); - private final EndpointDataForConnectorsService endpointDataForConnectorsService = new EndpointDataForConnectorsService( - edcSubmodelFacade); + private final EndpointDataForConnectorsService sut = new EndpointDataForConnectorsService(edcSubmodelFacade); @Test void shouldReturnExpectedEndpointDataReference() throws EdcRetrieverException { - // given + + // GIVEN when(edcSubmodelFacade.getEndpointReferenceForAsset(connectionOneAddress, DT_REGISTRY_ASSET_TYPE, - DT_REGISTRY_ASSET_VALUE)).thenReturn( - EndpointDataReference.Builder.newInstance().endpoint(connectionOneAddress).build()); + DT_REGISTRY_ASSET_VALUE)).thenReturn(CONNECTION_ONE_DATA_REF); - // when - final EndpointDataReference endpointDataReference = endpointDataForConnectorsService.findEndpointDataForConnectors( + // WHEN + final List> endpointDataReferences = sut.createFindEndpointDataForConnectorsFutures( Collections.singletonList(connectionOneAddress)); - // then - assertThat(endpointDataReference).isNotNull(); - assertThat(endpointDataReference.getEndpoint()).isEqualTo(connectionOneAddress); + // THEN + assertThat(endpointDataReferences).isNotEmpty() + .extracting(CompletableFuture::get) + .isNotEmpty() + .extracting(EndpointDataReference::getEndpoint) + .contains(connectionOneAddress); } @Test void shouldReturnExpectedEndpointDataReferenceFromSecondConnectionEndpoint() throws EdcRetrieverException { - // given + + // GIVEN + + // a first endpoint failing (1) when(edcSubmodelFacade.getEndpointReferenceForAsset(connectionOneAddress, DT_REGISTRY_ASSET_TYPE, DT_REGISTRY_ASSET_VALUE)).thenThrow( new EdcRetrieverException(new EdcClientException("EdcClientException"))); - when(edcSubmodelFacade.getEndpointReferenceForAsset(connectionTwoAddress, DT_REGISTRY_ASSET_TYPE, - DT_REGISTRY_ASSET_VALUE)).thenReturn( - EndpointDataReference.Builder.newInstance().endpoint(connectionTwoAddress).build()); - // when - final EndpointDataReference endpointDataReference = endpointDataForConnectorsService.findEndpointDataForConnectors( - List.of(connectionOneAddress, connectionTwoAddress)); + // and a second endpoint returning successfully (2) + when(edcSubmodelFacade.getEndpointReferenceForAsset(connectionTwoAddress, DT_REGISTRY_ASSET_TYPE, + DT_REGISTRY_ASSET_VALUE)).thenReturn(CONNECTION_TWO_DATA_REF); + + // WHEN + final List> dataRefFutures = // + sut.createFindEndpointDataForConnectorsFutures(List.of(connectionOneAddress, // (1) + connectionTwoAddress // (2) + )); + + // THEN + final List dataReferences = // + dataRefFutures.stream() + .map(EndpointDataForConnectorsServiceTest::executeFutureMappingErrorsToNull) + .filter(Objects::nonNull) + .toList(); + + assertThat(dataReferences).isNotEmpty() // + .extracting(EndpointDataReference::getEndpoint) // + .contains(connectionTwoAddress); + } - // then - assertThat(endpointDataReference).isNotNull(); - assertThat(endpointDataReference.getEndpoint()).isEqualTo(connectionTwoAddress); + private static EndpointDataReference executeFutureMappingErrorsToNull( + final CompletableFuture future) { + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + // ignore + return null; + } } @Test void shouldThrowExceptionWhenConnectorEndpointsNotReachable() throws EdcRetrieverException { - // given + + // GIVEN when(edcSubmodelFacade.getEndpointReferenceForAsset(anyString(), eq(DT_REGISTRY_ASSET_TYPE), eq(DT_REGISTRY_ASSET_VALUE))).thenThrow( new EdcRetrieverException(new EdcClientException("EdcClientException"))); - final List connectorEndpoints = List.of(connectionOneAddress, connectionTwoAddress); - // when + then - assertThatThrownBy( - () -> endpointDataForConnectorsService.findEndpointDataForConnectors(connectorEndpoints)).isInstanceOf( - RestClientException.class).hasMessageContainingAll(connectionOneAddress, connectionTwoAddress); + // WHEN + final var exceptions = new ArrayList<>(); + + // THEN + final List connectorEndpoints = List.of(connectionOneAddress, connectionTwoAddress); + sut.createFindEndpointDataForConnectorsFutures(connectorEndpoints) // + .forEach(future -> { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + exceptions.add(e); + } + }); + + assertThat(exceptions).hasSize(connectorEndpoints.size()); } } diff --git a/irs-testing/pom.xml b/irs-testing/pom.xml index 4bff31d728..7370be9eca 100644 --- a/irs-testing/pom.xml +++ b/irs-testing/pom.xml @@ -34,6 +34,7 @@ + org.springframework.boot @@ -53,6 +54,10 @@ + + org.springframework.boot + spring-boot-starter-web + net.minidev @@ -69,11 +74,23 @@ net.datafaker datafaker ${datafaker.version} + + + snakeyaml + org.yaml + + org.eclipse.tractusx.irs irs-models ${irs-registry-client.version} + + + snakeyaml + org.yaml + + com.fasterxml.jackson.datatype @@ -91,5 +108,10 @@ org.testcontainers junit-jupiter + + org.wiremock + wiremock-standalone + ${wiremock-standalone.version} + diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java new file mode 100644 index 0000000000..0d11ffb12e --- /dev/null +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java @@ -0,0 +1,101 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.testing.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; + +import java.util.List; + +import com.github.tomakehurst.wiremock.client.MappingBuilder; + +/** + * WireMock configurations and requests used for testing the Discovery Service flow. + */ +public final class DiscoveryServiceWiremockSupport { + public static final String CONTROLPLANE_PUBLIC_URL = "https://test.edc.io"; + public static final String EDC_DISCOVERY_PATH = "/edcDiscovery"; + public static final String TEST_BPN = "BPNL00000000TEST"; + public static final String DISCOVERY_FINDER_PATH = "/discoveryFinder"; + public static final String DISCOVERY_HOST = "http://discovery.finder"; + public static final String EDC_DISCOVERY_URL = DISCOVERY_HOST + EDC_DISCOVERY_PATH; + public static final String DISCOVERY_FINDER_URL = DISCOVERY_HOST + DISCOVERY_FINDER_PATH; + public static final int STATUS_CODE_OK = 200; + public static final int STATUS_CODE_NOT_FOUND = 404; + + private DiscoveryServiceWiremockSupport() { + } + + public static MappingBuilder postEdcDiscovery200() { + return postEdcDiscovery200(TEST_BPN, List.of(CONTROLPLANE_PUBLIC_URL)); + } + + public static MappingBuilder postEdcDiscoveryEmpty200() { + return postEdcDiscovery200(TEST_BPN, List.of()); + } + + public static MappingBuilder postEdcDiscovery200(final String bpn, final List edcUrls) { + return post(urlPathEqualTo(EDC_DISCOVERY_PATH)).willReturn( + responseWithStatus(STATUS_CODE_OK).withBody(edcDiscoveryResponse(bpn, edcUrls))); + } + + public static String edcDiscoveryResponse(final String bpn, final List connectorEndpoints) { + return """ + [ + { + "bpn": "%s", + "connectorEndpoint": [ + %s + ] + } + ] + """.formatted(bpn, String.join(",\n", connectorEndpoints.stream().map(s -> "\"" + s + "\"").toList())); + } + + public static MappingBuilder postDiscoveryFinder200() { + return post(urlPathEqualTo(DISCOVERY_FINDER_PATH)).willReturn( + responseWithStatus(STATUS_CODE_OK).withBody(discoveryFinderResponse(EDC_DISCOVERY_URL))); + } + + public static String discoveryFinderResponse(final String discoveryFinderUrl) { + return """ + { + "endpoints": [ + { + "type": "bpn", + "description": "Service to discover EDC to a particular BPN", + "endpointAddress": "%s", + "documentation": "http://.../swagger/index.html", + "resourceId": "316417cd-0fb5-4daf-8dfa-8f68125923f1" + } + ] + } + """.formatted(discoveryFinderUrl); + } + + public static MappingBuilder postDiscoveryFinder404() { + return post(urlPathEqualTo(DISCOVERY_FINDER_PATH)).willReturn(responseWithStatus(STATUS_CODE_NOT_FOUND)); + } + + public static MappingBuilder postEdcDiscovery404() { + return post(urlPathEqualTo(EDC_DISCOVERY_PATH)).willReturn(responseWithStatus(STATUS_CODE_NOT_FOUND)); + } +} diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DtrWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DtrWiremockSupport.java new file mode 100644 index 0000000000..f269e1713b --- /dev/null +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DtrWiremockSupport.java @@ -0,0 +1,209 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.testing.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; + +import java.util.List; + +import com.github.tomakehurst.wiremock.client.MappingBuilder; + +/** + * WireMock configurations and requests used for testing the decentralized DigitalTwinRegistry flow. + */ +public final class DtrWiremockSupport { + public static final String DATAPLANE_URL = "http://dataplane.test"; + public static final String DATAPLANE_PUBLIC_PATH = "/api/public"; + public static final String DATAPLANE_PUBLIC_URL = DATAPLANE_URL + DATAPLANE_PUBLIC_PATH; + public static final String SHELL_DESCRIPTORS_PATH = "/shell-descriptors/"; + public static final String PUBLIC_SHELL_DESCRIPTORS_PATH = DATAPLANE_PUBLIC_PATH + SHELL_DESCRIPTORS_PATH; + public static final String SHELL_DESCRIPTORS_TEMPLATE = SHELL_DESCRIPTORS_PATH + "{aasIdentifier}"; + public static final String LOOKUP_SHELLS_PATH = "/lookup/shells"; + public static final String PUBLIC_LOOKUP_SHELLS_PATH = DATAPLANE_PUBLIC_PATH + LOOKUP_SHELLS_PATH; + public static final String LOOKUP_SHELLS_TEMPLATE = LOOKUP_SHELLS_PATH + "?assetIds={assetIds}"; + public static final int STATUS_CODE_OK = 200; + public static final int STATUS_CODE_NOT_FOUND = 404; + + private DtrWiremockSupport() { + } + + public static MappingBuilder getShellDescriptor200() { + return getShellDescriptor200(SHELL_DESCRIPTORS_PATH + ".*"); + } + + public static MappingBuilder getShellDescriptor200(final String urlRegex) { + final String materialForRecycling = submodelDescriptor(DATAPLANE_PUBLIC_URL, + "urn:uuid:19b0338f-6d03-4198-b3b8-5c43f8958d60", DiscoveryServiceWiremockSupport.CONTROLPLANE_PUBLIC_URL, + "MaterialForRecycling", "urn:uuid:cf06d5d5-e3f8-4bd4-bfcf-81815310701f", + "urn:bamm:io.catenax.material_for_recycling:1.1.0#MaterialForRecycling"); + + final String batch = submodelDescriptor(DATAPLANE_PUBLIC_URL, "urn:uuid:234edd2f-0223-47c7-9fe4-3984ab14c4f9", + DiscoveryServiceWiremockSupport.CONTROLPLANE_PUBLIC_URL, "Batch", + "urn:uuid:f53db6ef-7a58-4326-9169-0ae198b85dbf", "urn:samm:io.catenax.batch:2.0.0#Batch"); + + final String singleLevelBomAsBuilt = submodelDescriptor(DATAPLANE_PUBLIC_URL, + "urn:uuid:234edd2f-0223-47c7-9fe4-3984ab14c4f9", DiscoveryServiceWiremockSupport.CONTROLPLANE_PUBLIC_URL, + "SingleLevelBomAsBuilt", "urn:uuid:0e413809-966b-4107-aae5-aeb28bcdaadf", + "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt"); + + final List submodelDescriptors = List.of(batch, singleLevelBomAsBuilt, materialForRecycling); + final List specificAssetIds = List.of(specificAssetId("manufacturerId", "BPNL00000003B0Q0"), + specificAssetId("batchId", "BID12345678")); + return get(urlPathMatching(urlRegex)).willReturn(responseWithStatus(STATUS_CODE_OK).withBody( + assetAdministrationShellResponse(submodelDescriptors, "urn:uuid:7e4541ea-bb0f-464c-8cb3-021abccbfaf5", + "EngineeringPlastics", "urn:uuid:9ce43b21-75e3-4cea-b13e-9a34f4f6822a", specificAssetIds))); + } + + @SuppressWarnings("PMD.UseObjectForClearerAPI") // used only for testing + public static MappingBuilder getShellDescriptor200(final String urlRegex, final String bpn, final List submodelDescriptors, + final String globalAssetId, final String shellId, final String idShort) { + final List specificAssetIds = List.of(specificAssetId("manufacturerId", bpn)); + return get(urlPathMatching(urlRegex)).willReturn(responseWithStatus(STATUS_CODE_OK).withBody( + assetAdministrationShellResponse(submodelDescriptors, globalAssetId, idShort, shellId, specificAssetIds))); + } + + public static String assetAdministrationShellResponse(final List submodelDescriptors, + final String globalAssetId, final String idShort, final String shellId, + final List specificAssetIds) { + return """ + { + "description": [], + "displayName": [], + "globalAssetId": "%s", + "idShort": "%s", + "id": "%s", + "specificAssetIds": [ + %s + ], + "submodelDescriptors": [ + %s + ] + } + """.formatted(globalAssetId, idShort, shellId, String.join(",\n", specificAssetIds), + String.join(",\n", submodelDescriptors)); + } + + public static String specificAssetId(final String key, final String value) { + return """ + { + "supplementalSemanticIds": [], + "name": "%s", + "value": "%s", + "externalSubjectId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "BPNL00000001CRHK" + } + ] + } + } + """.formatted(key, value); + } + + @SuppressWarnings("PMD.UseObjectForClearerAPI") // used only for testing + public static String submodelDescriptor(final String dataplaneUrl, final String assetId, final String dspEndpoint, + final String idShort, final String submodelDescriptorId, final String semanticId) { + final String href = dataplaneUrl + "/" + submodelDescriptorId; + return """ + { + "endpoints": [ + { + "interface": "SUBMODEL-3.0", + "protocolInformation": { + "href": "%s", + "endpointProtocol": "HTTP", + "endpointProtocolVersion": [ + "1.1" + ], + "subprotocol": "DSP", + "subprotocolBody": "id=%s;dspEndpoint=%s", + "subprotocolBodyEncoding": "plain", + "securityAttributes": [ + { + "type": "NONE", + "key": "NONE", + "value": "NONE" + } + ] + } + } + ], + "idShort": "%s", + "id": "%s", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "%s" + } + ] + }, + "supplementalSemanticId": [], + "description": [], + "displayName": [] + } + """.formatted(href, assetId, dspEndpoint, idShort, submodelDescriptorId, semanticId); + } + + public static MappingBuilder getLookupShells200() { + return getLookupShells200(LOOKUP_SHELLS_PATH); + } + + public static MappingBuilder getLookupShells200(final String lookupShellsPath) { + return get(urlPathEqualTo(lookupShellsPath)).willReturn(responseWithStatus(STATUS_CODE_OK).withBody( + lookupShellsResponse(List.of("urn:uuid:21f7ebea-fa8a-410c-a656-bd9082e67dcf")))); + } + + public static MappingBuilder getLookupShells200(final String lookupShellsPath, final List shellIds) { + return get(urlPathEqualTo(lookupShellsPath)).willReturn(responseWithStatus(STATUS_CODE_OK).withBody( + lookupShellsResponse(shellIds))); + } + + public static MappingBuilder getLookupShells200Empty() { + return get(urlPathMatching(LOOKUP_SHELLS_PATH + ".*")).willReturn( + responseWithStatus(STATUS_CODE_OK).withBody(lookupShellsResponse(List.of()))); + } + + public static String lookupShellsResponse(final List shellIds) { + return """ + { + "paging_metadata": {}, + "result": [ + %s + ] + } + """.formatted(String.join(",\n", shellIds.stream().map(s -> "\"" + s + "\"").toList())); + } + + public static MappingBuilder getLookupShells404() { + return get(urlPathEqualTo(LOOKUP_SHELLS_PATH)).willReturn(responseWithStatus(STATUS_CODE_NOT_FOUND)); + } + + public static MappingBuilder getShellDescriptor404() { + return get(urlPathMatching(SHELL_DESCRIPTORS_PATH + ".*")).willReturn( + responseWithStatus(STATUS_CODE_NOT_FOUND)); + } +} diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java new file mode 100644 index 0000000000..cd38948422 --- /dev/null +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java @@ -0,0 +1,246 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.testing.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; + +import java.util.List; + +/** + * WireMock configurations and requests used for testing the EDC Flow. + */ +public final class SubmodelFacadeWiremockSupport { + public static final String PATH_CATALOG = "/catalog/request"; + public static final String PATH_NEGOTIATE = "/contractnegotiations"; + public static final String PATH_TRANSFER = "/transferprocesses"; + public static final String PATH_STATE = "/state"; + public static final String PATH_DATAPLANE_PUBLIC = "/api/public"; + public static final String DATAPLANE_HOST = "http://provider.dataplane"; + public static final String CONTEXT = """ + { + "dct": "https://purl.org/dc/terms/", + "tx": "https://w3id.org/tractusx/v0.0.1/ns/", + "edc": "https://w3id.org/edc/v0.0.1/ns/", + "dcat": "https://www.w3.org/ns/dcat/", + "odrl": "http://www.w3.org/ns/odrl/2/", + "dspace": "https://w3id.org/dspace/v0.8/" + }"""; + public static final String EDC_PROVIDER_DUMMY_URL = "https://edc.io/api/v1/dsp"; + public static final String IRS_INTERNAL_CALLBACK_URL = "https://irs.test/internal/endpoint-data-reference"; + public static final String EDC_PROVIDER_BPN = "BPNL00000003CRHK"; + public static final int STATUS_CODE_OK = 200; + + private SubmodelFacadeWiremockSupport() { + } + + public static String prepareNegotiation() { + final String contractAgreementId = "7681f966-36ea-4542-b5ea-0d0db81967de:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-31b614f5-ec14-4ed2-a509-e7b7780083e7:a6144a2e-c1b1-4ec6-96e1-a221da134e4f"; + prepareNegotiation("1bbaec6e-c316-4e1e-8258-c07a648cc43c", "1b21e963-0bc5-422a-b30d-fd3511861d88", + contractAgreementId, + "5a7ab616-989f-46ae-bdf2-32027b9f6ee6-31b614f5-ec14-4ed2-a509-e7b7780083e7"); + return contractAgreementId; + } + + @SuppressWarnings("PMD.UseObjectForClearerAPI") // used only for testing + public static void prepareNegotiation(final String negotiationId, final String transferProcessId, + final String contractAgreementId, final String edcAssetId) { + stubFor(post(urlPathEqualTo(PATH_CATALOG)).willReturn(WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody(getCatalogResponse(edcAssetId, + "USE", EDC_PROVIDER_BPN)))); + + stubFor(post(urlPathEqualTo(PATH_NEGOTIATE)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK).withBody(startNegotiationResponse(negotiationId)))); + + final String negotiationState = "FINALIZED"; + stubFor(get(urlPathEqualTo(PATH_NEGOTIATE + "/" + negotiationId)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody(getNegotiationConfirmedResponse(negotiationId, negotiationState, + contractAgreementId)))); + + stubFor(get(urlPathEqualTo(PATH_NEGOTIATE + "/" + negotiationId + PATH_STATE)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody(getNegotiationStateResponse(negotiationState)))); + + stubFor(post(urlPathEqualTo(PATH_TRANSFER)).willReturn(WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody(startTransferProcessResponse( + transferProcessId)) + + )); + final String transferProcessState = "COMPLETED"; + stubFor(get(urlPathEqualTo(PATH_TRANSFER + "/" + transferProcessId + PATH_STATE)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody(getTransferProcessStateResponse(transferProcessState)))); + stubFor(get(urlPathEqualTo(PATH_TRANSFER + "/" + transferProcessId)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody( + getTransferConfirmedResponse(transferProcessId, transferProcessState, edcAssetId, + contractAgreementId)))); + } + + private static String startTransferProcessResponse(final String transferProcessId) { + return startNegotiationResponse(transferProcessId); + } + + private static String startNegotiationResponse(final String negotiationId) { + return """ + { + "@type": "edc:IdResponseDto", + "@id": "%s", + "edc:createdAt": 1686830151573, + "@context": %s + } + """.formatted(negotiationId, CONTEXT); + } + + private static String getNegotiationStateResponse(final String negotiationState) { + return stateResponseTemplate("edc:NegotiationState", negotiationState); + } + + private static String getTransferProcessStateResponse(final String transferProcessState) { + return stateResponseTemplate("edc:TransferState", transferProcessState); + } + + private static String stateResponseTemplate(final String responseType, final String negotiationState) { + return """ + { + "@type": "%s", + "edc:state": "%s", + "@context": %s + } + """.formatted(responseType, negotiationState, CONTEXT); + } + + private static String getNegotiationConfirmedResponse(final String negotiationId, final String negotiationState, + final String contractAgreementId) { + return """ + { + "@type": "edc:ContractNegotiationDto", + "@id": "%s", + "edc:type": "CONSUMER", + "edc:protocol": "dataspace-protocol-http", + "edc:state": "%s", + "edc:counterPartyAddress": "%s", + "edc:callbackAddresses": [], + "edc:contractAgreementId": "%s", + "@context": %s + } + """.formatted(negotiationId, negotiationState, EDC_PROVIDER_DUMMY_URL, contractAgreementId, CONTEXT); + } + + private static String getTransferConfirmedResponse(final String transferProcessId, final String transferState, + final String edcAssetId, final String contractAgreementId) { + return """ + { + "@id": "%s", + "@type": "edc:TransferProcessDto", + "edc:state": "%s", + "edc:stateTimestamp": 1688024335567, + "edc:type": "CONSUMER", + "edc:callbackAddresses": [], + "edc:dataDestination": { + "edc:type": "HttpProxy" + }, + "edc:dataRequest": { + "@type": "edc:DataRequestDto", + "@id": "%s", + "edc:assetId": "%s", + "edc:contractId": "%s", + "edc:connectorId": "%s" + }, + "edc:receiverHttpEndpoint": "%s", + "@context": %s + } + """.formatted(transferProcessId, transferState, transferProcessId, edcAssetId, contractAgreementId, + EDC_PROVIDER_BPN, IRS_INTERNAL_CALLBACK_URL, CONTEXT); + } + + public static String getCatalogResponse(final String edcAssetId, final String permissionType, + final String edcProviderBpn) { + return """ + { + "@id": "78ff625c-0c05-4014-965c-bd3d0a6a0de0", + "@type": "dcat:Catalog", + "dcat:dataset": { + "@id": "58505404-4da1-427a-82aa-b79482bcd1f0", + "@type": "dcat:Dataset", + "odrl:hasPolicy": { + "@id": "7681f966-36ea-4542-b5ea-0d0db81967de:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-31b614f5-ec14-4ed2-a509-e7b7780083e7:66131c58-32af-4df0-825d-77f7df6017c1", + "@type": "odrl:Set", + "odrl:permission": { + "odrl:target": "%s", + "odrl:action": { + "odrl:type": "%s" + }, + "odrl:constraint": %s + }, + "odrl:prohibition": [], + "odrl:obligation": [], + "odrl:target": "%s" + }, + "dcat:distribution": [ + { + "@type": "dcat:Distribution", + "dct:format": { + "@id": "HttpProxy" + }, + "dcat:accessService": "4ba1faa1-7f1a-4fb7-a41c-317f450e7443" + } + ], + "edc:description": "IRS EDC Test Asset", + "edc:id": "%s" + }, + "dcat:service": { + "@id": "4ba1faa1-7f1a-4fb7-a41c-317f450e7443", + "@type": "dcat:DataService", + "dct:terms": "connector", + "dct:endpointUrl": "%s" + }, + "edc:participantId": "%s", + "@context": %s + } + """.formatted(edcAssetId, permissionType, createConstraints(), edcAssetId, edcAssetId, + EDC_PROVIDER_DUMMY_URL, edcProviderBpn, CONTEXT); + } + + private static String createConstraints() { + final List atomitConstraints = List.of(createAtomicConstraint("Membership", "active"), + createAtomicConstraint("FrameworkAgreement.traceability", "active")); + return """ + { + "odrl:and": [ + %s + ] + }""".formatted(String.join(",\n", atomitConstraints)); + } + + private static String createAtomicConstraint(final String leftOperand, final String rightOperand) { + return """ + { + "odrl:leftOperand": "%s", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "%s" + }""".formatted(leftOperand, rightOperand); + } +} diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/WireMockConfig.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/WireMockConfig.java new file mode 100644 index 0000000000..5da7750813 --- /dev/null +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/WireMockConfig.java @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 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.irs.testing.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * Common configurations for Wiremock tests. + */ +public final class WireMockConfig { + private WireMockConfig() { + } + + /** + * Configured RestTemplate which proxies all requests to the provided host / port. + * + * @param proxyServerHost the host where all requests will be proxied to + * @param httpPort the port of the host where all requests will be proxied to + * @return the configured {@link RestTemplate} + */ + public static RestTemplate restTemplateProxy(final String proxyServerHost, final int httpPort) { + final Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyServerHost, httpPort)); + final SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + requestFactory.setProxy(proxy); + return new RestTemplate(requestFactory); + } + + public static ResponseDefinitionBuilder responseWithStatus(final int statusCode) { + return aResponse().withStatus(statusCode).withHeader("Content-Type", "application/json;charset=UTF-8"); + } +} diff --git a/local/testing/IRS_Request_Collection.json b/local/testing/IRS_Request_Collection.json index 2223c72d5f..246addc92b 100644 --- a/local/testing/IRS_Request_Collection.json +++ b/local/testing/IRS_Request_Collection.json @@ -1,12 +1,12 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2024-01-23T10:32:11.813Z", + "__export_date": "2024-02-05T09:54:46.629Z", "__export_source": "insomnia.desktop.app:v8.6.0", "resources": [ { - "_id": "req_1267e02eed7b49a28ea7e8b70bfe1cf6", - "parentId": "fld_9988da1681c94a2a8ce76b85d744325f", + "_id": "req_168bf1cb28a043d18f376c8d93c82d1a", + "parentId": "fld_ce20d8a3c9d441178febcb303a052551", "modified": 1705006817408, "created": 1705005887617, "url": "{{IRS_HOST}}/irs/policies", @@ -35,8 +35,8 @@ "_type": "request" }, { - "_id": "fld_9988da1681c94a2a8ce76b85d744325f", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_ce20d8a3c9d441178febcb303a052551", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1687243055015, "created": 1687243055015, "name": "Policy Store", @@ -47,8 +47,8 @@ "_type": "request_group" }, { - "_id": "fld_1810e742cdde46ab9d50b692c5b8da98", - "parentId": "wrk_565df8abe30f4da29d8bffcde97927d7", + "_id": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", + "parentId": "wrk_dbeadb1e56e942299e6c920265e6c667", "modified": 1691572726194, "created": 1680682418636, "name": "IRS DEMO Collection", @@ -59,18 +59,18 @@ "_type": "request_group" }, { - "_id": "wrk_565df8abe30f4da29d8bffcde97927d7", + "_id": "wrk_dbeadb1e56e942299e6c920265e6c667", "parentId": null, - "modified": 1680682438221, - "created": 1680682419747, + "modified": 1706524702729, + "created": 1706524692257, "name": "IRS", "description": "", "scope": "collection", "_type": "workspace" }, { - "_id": "req_f4ed2f142c40439998f72e2ceb85380e", - "parentId": "fld_9988da1681c94a2a8ce76b85d744325f", + "_id": "req_d9aec188d27d4e26922788e029f48cb3", + "parentId": "fld_ce20d8a3c9d441178febcb303a052551", "modified": 1702990529632, "created": 1687243204155, "url": "{{IRS_HOST}}/irs/policies/{% prompt 'policyId', '', 'traceability-test', '', false, true %}", @@ -99,8 +99,8 @@ "_type": "request" }, { - "_id": "req_990267ff2ba049fa8e0bfff912088b84", - "parentId": "fld_9988da1681c94a2a8ce76b85d744325f", + "_id": "req_f23abf2cce914010bc0a1b6cca091ad2", + "parentId": "fld_ce20d8a3c9d441178febcb303a052551", "modified": 1702990565006, "created": 1693576003390, "url": "{{IRS_HOST}}/irs/policies/{% prompt 'id', '', 'traceability-test', '', false, true %}", @@ -137,9 +137,9 @@ "_type": "request" }, { - "_id": "req_73b6af41486b4910883d91dea050839e", - "parentId": "fld_9988da1681c94a2a8ce76b85d744325f", - "modified": 1705006437142, + "_id": "req_5d5596ef316341198185c3dab431235c", + "parentId": "fld_ce20d8a3c9d441178febcb303a052551", + "modified": 1707126688041, "created": 1687243182397, "url": "{{IRS_HOST}}/irs/policies", "name": "Register policy", @@ -147,7 +147,7 @@ "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n \"permissions\": [\n {\n \"action\": \"USE\",\n \"constraints\": [\n {\n \"and\": [\n {\n \"leftOperand\": \"FrameworkAgreement.traceability\",\n \"operator\": \"eq\",\n \"rightOperand\": [\n \"active\"\n ]\n },\n {\n \"leftOperand\": \"Membership\",\n \"operator\": \"eq\",\n \"rightOperand\": [\n \"active\"\n ]\n }\n ]\n }\n ]\n }\n ],\n \"policyId\": \"traceability-test\",\n \"validUntil\": \"2024-12-12T23:59:59.999Z\"\n}" + "text": "{\n\t\"validUntil\": \"2025-12-12T23:59:59.999Z\",\n\t\"payload\": {\n\t\t\"@context\": {\n\t\t\t\"odrl\": \"http://www.w3.org/ns/odrl/2/\"\n\t\t},\n\t\t\"@id\": \"policy-id12\",\n\t\t\"policy\": {\n\t\t\t\"odrl:permission\": [\n\t\t\t\t{\n\t\t\t\t\t\"odrl:action\": \"USE\",\n\t\t\t\t\t\"odrl:constraint\": {\n\t\t\t\t\t\t\"odrl:and\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"odrl:leftOperand\": \"Membership\",\n\t\t\t\t\t\t\t\t\"odrl:operator\": {\n\t\t\t\t\t\t\t\t\t\"@id\": \"odrl:eq\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"odrl:rightOperand\": \"active\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"odrl:leftOperand\": \"PURPOSE\",\n\t\t\t\t\t\t\t\t\"odrl:operator\": {\n\t\t\t\t\t\t\t\t\t\"@id\": \"odrl:eq\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"odrl:rightOperand\": \"ID 3.1 Trace\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t}\n}" }, "parameters": [], "headers": [ @@ -175,8 +175,8 @@ "_type": "request" }, { - "_id": "req_d5741df44ed441d39c6b9325e6cb60ee", - "parentId": "fld_bb755d1cb6a244aca3c64d9a7e5ac10b", + "_id": "req_0ea20f07e6384c87aec64b664f1f1936", + "parentId": "fld_4990991deee940668b0772802a92dad7", "modified": 1706003275081, "created": 1680682418619, "url": "{{DIGITAL_TWIN_REGISTRY}}/api/v3.0/shell-descriptors/{% prompt 'aasIdentifier', '', _.GLOBAL_ASSET_ID, '', false, true %}", @@ -207,8 +207,8 @@ "_type": "request" }, { - "_id": "fld_bb755d1cb6a244aca3c64d9a7e5ac10b", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_4990991deee940668b0772802a92dad7", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1691504187689, "created": 1680682418630, "name": "Digital Twin Registry", @@ -219,8 +219,8 @@ "_type": "request_group" }, { - "_id": "req_c9f7bc41a36e4d41b9bf26073e7feb98", - "parentId": "fld_bb755d1cb6a244aca3c64d9a7e5ac10b", + "_id": "req_645f097057dd4d0b8a94530853402acc", + "parentId": "fld_4990991deee940668b0772802a92dad7", "modified": 1706002920212, "created": 1690529035794, "url": "{{DIGITAL_TWIN_REGISTRY}}/api/v3.0/shell-descriptors", @@ -251,8 +251,8 @@ "_type": "request" }, { - "_id": "req_4d2cf605282f41ae82adef74435aec12", - "parentId": "fld_bb755d1cb6a244aca3c64d9a7e5ac10b", + "_id": "req_b3e47af3e76746539ca0bf30c60e39ea", + "parentId": "fld_4990991deee940668b0772802a92dad7", "modified": 1706003278149, "created": 1680682418609, "url": "{{DIGITAL_TWIN_REGISTRY}}/api/v3.0/lookup/shells", @@ -331,8 +331,8 @@ "_type": "request" }, { - "_id": "req_1619abe9652b4b12b38aa24cbd40a7a1", - "parentId": "fld_bb755d1cb6a244aca3c64d9a7e5ac10b", + "_id": "req_d7172466c4bc48e986679e0895b8a2cc", + "parentId": "fld_4990991deee940668b0772802a92dad7", "modified": 1706003280850, "created": 1680682418595, "url": "{{DIGITAL_TWIN_REGISTRY}}/api/v3.0/lookup/shells", @@ -369,8 +369,8 @@ "_type": "request" }, { - "_id": "req_d6f383445f204a948970420dcda640d0", - "parentId": "fld_bb755d1cb6a244aca3c64d9a7e5ac10b", + "_id": "req_3e07acae12c54acb94dd7aa9a9237b00", + "parentId": "fld_4990991deee940668b0772802a92dad7", "modified": 1706003354109, "created": 1680682418581, "url": "{{DIGITAL_TWIN_REGISTRY}}/api/v3.0/lookup/shells", @@ -407,8 +407,8 @@ "_type": "request" }, { - "_id": "req_a6b3fee73a4c43e8a5e0449c3c620da7", - "parentId": "fld_bb755d1cb6a244aca3c64d9a7e5ac10b", + "_id": "req_b2e720c47f6145e9915b098cfb162bf4", + "parentId": "fld_4990991deee940668b0772802a92dad7", "modified": 1706003284715, "created": 1680682418570, "url": "{{DIGITAL_TWIN_REGISTRY}}/api/v3.0/lookup/shells/query", @@ -446,8 +446,8 @@ "_type": "request" }, { - "_id": "req_7245f1e26dea444f97742f993492e803", - "parentId": "fld_bb755d1cb6a244aca3c64d9a7e5ac10b", + "_id": "req_c25fd37611874fb9b9d614c5119fc9f2", + "parentId": "fld_4990991deee940668b0772802a92dad7", "modified": 1706003286642, "created": 1691408320970, "url": "{{DIGITAL_TWIN_REGISTRY}}/api/v3.0/lookup/shells/query", @@ -485,8 +485,8 @@ "_type": "request" }, { - "_id": "req_8ac0e8a9fe334e6a96dbb168a304ae18", - "parentId": "fld_bb755d1cb6a244aca3c64d9a7e5ac10b", + "_id": "req_3250e5def5654142a519de4427107bbd", + "parentId": "fld_4990991deee940668b0772802a92dad7", "modified": 1706003289343, "created": 1689167429413, "url": "{{DIGITAL_TWIN_REGISTRY}}/api/v3.0/shell-descriptors/{% prompt 'id', '', '', '', false, true %}", @@ -525,8 +525,8 @@ "_type": "request" }, { - "_id": "req_c1523dcf97d14b58bb72cf3253d4fb4b", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_e1cec062761b4714b9d79f95bfb4f052", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991054859, "created": 1680682418551, "url": "{{IRS_HOST}}/irs/jobs", @@ -563,8 +563,8 @@ "_type": "request" }, { - "_id": "fld_c517b707380249f3bd7290d8d22a9138", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_66bf58dc74c24069a012d954a5fcc046", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1680682418562, "created": 1680682418562, "name": "IRS Test Collection", @@ -575,8 +575,8 @@ "_type": "request_group" }, { - "_id": "req_dc3918d6fa9843ffb4fe8e268888a0c4", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_4c94d946a6ca458baf3953f64eb3d2f1", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991058493, "created": 1680682418539, "url": "{{IRS_HOST}}/irs/jobs", @@ -613,8 +613,8 @@ "_type": "request" }, { - "_id": "req_add3363de5c941c1864c49fa8313468e", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_eecb959367ec45538fed6c82871dff36", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991063641, "created": 1680682418524, "url": "{{IRS_HOST}}/irs/jobs", @@ -651,8 +651,8 @@ "_type": "request" }, { - "_id": "req_daffa8d4fd694e388086286b52583a30", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_0cf1f56543b043e0b6c2d60dc1339efb", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991067797, "created": 1680682418514, "url": "{{IRS_HOST}}/irs/jobs", @@ -689,8 +689,8 @@ "_type": "request" }, { - "_id": "req_4741e3623c0a4649807af10b573a1ffb", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_0dd4702af7f44308ae5fc38b1247ddb9", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991071709, "created": 1680682418504, "url": "{{IRS_HOST}}/irs/jobs", @@ -727,8 +727,8 @@ "_type": "request" }, { - "_id": "req_4103f9f43731449c964103eec0aee11d", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_57f0986d2fbd448a8763203a07f4e538", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991076696, "created": 1695042901876, "url": "{{IRS_HOST}}/irs/jobs", @@ -765,8 +765,8 @@ "_type": "request" }, { - "_id": "req_af000a718cd94c38aecbff4f182f5a0b", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_9eb404e09440411da201197459b718be", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991080833, "created": 1680682418488, "url": "{{IRS_HOST}}/irs/jobs", @@ -803,8 +803,8 @@ "_type": "request" }, { - "_id": "req_60460ded5ba94b33b7f9f2373cdea348", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_803a18dc7154479f99ca3323bc502004", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991084713, "created": 1680682418479, "url": "{{IRS_HOST}}/irs/jobs", @@ -841,8 +841,8 @@ "_type": "request" }, { - "_id": "req_4e91ca97acf14f019fac724fe25e1e6c", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_f4f50dc45f2d447fb198671a25fa554c", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991088495, "created": 1680682418469, "url": "{{IRS_HOST}}/irs/jobs", @@ -879,8 +879,8 @@ "_type": "request" }, { - "_id": "req_f269dfad4827462aae4bea5367b442e7", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_adb8c665373d432eb5868cdb54e005ae", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991094498, "created": 1680682418460, "url": "{{IRS_HOST}}/irs/jobs", @@ -917,8 +917,8 @@ "_type": "request" }, { - "_id": "req_1d5b7da6d6b54735a6166251c0a1edfb", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_4d5b921d3b65463c9413a037e19ae4e0", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991098918, "created": 1680682418451, "url": "{{IRS_HOST}}/irs/jobs", @@ -955,8 +955,8 @@ "_type": "request" }, { - "_id": "req_378c52bc81b64e8b95f641c4f655a1d5", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_c78f55430e3c4caa9db40afa18e0071e", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991103182, "created": 1680682418442, "url": "{{IRS_HOST}}/irs/jobs", @@ -993,8 +993,8 @@ "_type": "request" }, { - "_id": "req_38b875ef05d14595b892e65914a6df24", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_cd096c5dd862469ea857cfbaaec565a7", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991107672, "created": 1680682418432, "url": "{{IRS_HOST}}/irs/jobs", @@ -1031,8 +1031,8 @@ "_type": "request" }, { - "_id": "req_c96be1a03513429184f85283557a8d65", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_75498c5f7f4e49a9aa5eaed3f56efbfe", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991113717, "created": 1695192937155, "url": "{{IRS_HOST}}/irs/jobs", @@ -1069,8 +1069,8 @@ "_type": "request" }, { - "_id": "req_33ea4f68b3904bc6bb7ad50dde23632a", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_2c4bd212e2cc4072a77963d1c15bd3c0", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991120609, "created": 1695192971825, "url": "{{IRS_HOST}}/irs/jobs", @@ -1107,8 +1107,8 @@ "_type": "request" }, { - "_id": "req_e18ad8582e204828b06e0de32d30390e", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_daf9e35ba9f94a4abebba38d62c7ad57", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991125292, "created": 1680682418424, "url": "{{IRS_HOST}}/irs/jobs", @@ -1145,8 +1145,8 @@ "_type": "request" }, { - "_id": "req_d52411aa0a6249449a3e99c91b262e4e", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_e88261d8f4c3408183d52a94fe0f64aa", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991130711, "created": 1680682418414, "url": "{{IRS_HOST}}/irs/jobs", @@ -1183,8 +1183,8 @@ "_type": "request" }, { - "_id": "req_c610dd3176ae49d8a96884de371de3c9", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_253793b503264ec586dd5a3981902934", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991137603, "created": 1680682418401, "url": "{{IRS_HOST}}/irs/jobs", @@ -1221,8 +1221,8 @@ "_type": "request" }, { - "_id": "req_07403288d3394387926cd9f0926b3ea4", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_68fe9a6efec74ee9a167a895c8babb10", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991145487, "created": 1680682418392, "url": "{{IRS_HOST}}/irs/jobs", @@ -1259,8 +1259,8 @@ "_type": "request" }, { - "_id": "req_ae4eeade6f854a3b845f1c4b0d1f351d", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_6aac64b0a99e4ffabeb55f10353c357f", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991149742, "created": 1683184048412, "url": "{{IRS_HOST}}/irs/jobs", @@ -1297,8 +1297,8 @@ "_type": "request" }, { - "_id": "req_d2763c0b68e649debd563be84a2e7ac5", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_4138d3ef7c2842e2866fb9be242c4735", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991154342, "created": 1693493383337, "url": "{{IRS_HOST}}/irs/jobs", @@ -1335,8 +1335,8 @@ "_type": "request" }, { - "_id": "req_67e70efe4a904805b6d8d614401097f5", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_9956e174586d4716b2652ed693518241", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991159838, "created": 1693493584873, "url": "{{IRS_HOST}}/irs/jobs", @@ -1373,8 +1373,8 @@ "_type": "request" }, { - "_id": "req_329716a740284e9d92c0e98b08ef8c4d", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_8cc9f463cd5e456f8cc922b9cbfbf35c", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991164094, "created": 1693493594373, "url": "{{IRS_HOST}}/irs/jobs", @@ -1411,8 +1411,8 @@ "_type": "request" }, { - "_id": "req_1e84830bf707450c9aec67763df4db4d", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_162de6be38a749f99ad80c9d73a5062e", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991168363, "created": 1693493604521, "url": "{{IRS_HOST}}/irs/jobs", @@ -1449,8 +1449,8 @@ "_type": "request" }, { - "_id": "req_846efa768fa4440e92d62d103f8268f0", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_cb39bd2420084559a4b8df34efc0225a", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991173689, "created": 1680682418381, "url": "{{IRS_HOST}}/irs/jobs", @@ -1490,8 +1490,8 @@ "_type": "request" }, { - "_id": "req_f44e4fb2955e45b4822b3579a5953370", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_fa00c41986994e53b60fb66f6a7f9b13", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991177973, "created": 1680682418372, "url": "{{IRS_HOST}}/irs/jobs", @@ -1531,8 +1531,8 @@ "_type": "request" }, { - "_id": "req_94e9199eebac41d4aaa24b79a529e712", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_e4960d3aea4a45098a0ad41783ee8b3e", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991182139, "created": 1680682418358, "url": "{{IRS_HOST}}/irs/jobs", @@ -1572,8 +1572,8 @@ "_type": "request" }, { - "_id": "req_bead38a2831748b0908dea7f60b75fcd", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_fab648d9d45b426886a3f8d5d9e07610", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991186114, "created": 1680682418348, "url": "{{IRS_HOST}}/irs/jobs", @@ -1607,8 +1607,8 @@ "_type": "request" }, { - "_id": "req_3388b98dfadf4ca29be1a8c20eb32540", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_47ad9b23e8f646cfaa67e1c14123ed0b", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991190326, "created": 1680682418339, "url": "{{IRS_HOST}}/irs/jobs", @@ -1648,8 +1648,8 @@ "_type": "request" }, { - "_id": "req_690f2cd5e1ad46e497a8c25694fe37ec", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_097638186ae0403aa04692c9e282e14f", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991196283, "created": 1680682418325, "url": "{{IRS_HOST}}/irs/jobs/{% prompt 'Job ID', '', '', '', false, true %}", @@ -1689,8 +1689,8 @@ "_type": "request" }, { - "_id": "req_a232e153a891470e8b82c7e42a86f95f", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_0bb34d3d1ff547d1abe7f50a31b3f225", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991200399, "created": 1680682418316, "url": "{{IRS_HOST}}/irs/jobs/{% prompt 'Job ID', '', '', '', false, true %}", @@ -1724,8 +1724,8 @@ "_type": "request" }, { - "_id": "req_8448f684adbd409eaeb7c017af4b1508", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_a94ae8d8ebb94f758ab6196c97e9061c", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991205447, "created": 1680682418307, "url": "{{IRS_HOST}}/irs/jobs/test", @@ -1759,8 +1759,8 @@ "_type": "request" }, { - "_id": "req_0a5dc1223ebc49ca9aed7114eb470dd7", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_1b10ff658962459db2c6c1b704ff68b8", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991214202, "created": 1680682418297, "url": "{{IRS_HOST}}/irs/jobs/00000000-0000-0000-0000-000000000000", @@ -1794,8 +1794,8 @@ "_type": "request" }, { - "_id": "req_bc8199a1ea134828a0c61c8ce39e5f94", - "parentId": "fld_c517b707380249f3bd7290d8d22a9138", + "_id": "req_fe2049dcfd6b4c06a36069d0bf530e20", + "parentId": "fld_66bf58dc74c24069a012d954a5fcc046", "modified": 1702991218362, "created": 1680682418280, "url": "{{IRS_HOST}}/irs/jobs/{% prompt 'Job ID', '', '', '', false, true %}", @@ -1829,8 +1829,8 @@ "_type": "request" }, { - "_id": "req_71ca4eb08f894036866e35931ca370c9", - "parentId": "fld_476c769969fd4ad7a5d83e31b2a861d9", + "_id": "req_c1a1e632e4364bf3928104eb2700f8a0", + "parentId": "fld_366bdc4025de4610b245e7bc85b90b58", "modified": 1705942015684, "created": 1682672699249, "url": "{{ _.BPN_DISCOVERY }}/api/administration/connectors/bpnDiscovery/search", @@ -1868,8 +1868,8 @@ "_type": "request" }, { - "_id": "fld_476c769969fd4ad7a5d83e31b2a861d9", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_366bdc4025de4610b245e7bc85b90b58", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1683630931664, "created": 1683630887514, "name": "Discovery", @@ -1880,8 +1880,8 @@ "_type": "request_group" }, { - "_id": "req_d7248e0a7b1f4393a5a390b265482415", - "parentId": "fld_476c769969fd4ad7a5d83e31b2a861d9", + "_id": "req_75de594fc67c4e0e89af774b81c3f0af", + "parentId": "fld_366bdc4025de4610b245e7bc85b90b58", "modified": 1705942027574, "created": 1683031718699, "url": "{{ _.DISCOVERY_FINDER }}/api/administration/connectors/discovery/search", @@ -1919,8 +1919,8 @@ "_type": "request" }, { - "_id": "req_77fa47273bf343c7b674d6f6062a4699", - "parentId": "fld_476c769969fd4ad7a5d83e31b2a861d9", + "_id": "req_afa03ef38dd34df8ad7febd021e97b2a", + "parentId": "fld_366bdc4025de4610b245e7bc85b90b58", "modified": 1705942036978, "created": 1683560906453, "url": "{{ _.EDC_DISCOVERY }}/api/administration/connectors/discovery", @@ -1958,8 +1958,8 @@ "_type": "request" }, { - "_id": "req_b02ac0bfc4704c83a5c5f8b24175d61a", - "parentId": "fld_a78746c77e5042fe9a16e9c07c428503", + "_id": "req_9d0281b262424f4b8c1ba62e11bb2fa3", + "parentId": "fld_0fe6ae4d2f4e4ce4b370fd2792670219", "modified": 1705006665175, "created": 1680682418265, "url": "{{IRS_HOST}}/irs/jobs", @@ -1996,8 +1996,8 @@ "_type": "request" }, { - "_id": "fld_a78746c77e5042fe9a16e9c07c428503", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_0fe6ae4d2f4e4ce4b370fd2792670219", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1680682418273, "created": 1680682418273, "name": "IRS Basic API Calls", @@ -2008,8 +2008,8 @@ "_type": "request_group" }, { - "_id": "req_8ba77f0c79bd4a3cbb0b9f661a05b03e", - "parentId": "fld_a78746c77e5042fe9a16e9c07c428503", + "_id": "req_54c394982fa249618bb7bb46aea068a7", + "parentId": "fld_0fe6ae4d2f4e4ce4b370fd2792670219", "modified": 1705942154792, "created": 1680682418238, "url": "{{IRS_HOST}}/irs/jobs/{% response 'body', 'req_b02ac0bfc4704c83a5c5f8b24175d61a', 'b64::JC5pZA==::46b', 'never', 60 %} ", @@ -2049,8 +2049,8 @@ "_type": "request" }, { - "_id": "req_58c35dbebe62459182db7768eb9cfe38", - "parentId": "fld_a78746c77e5042fe9a16e9c07c428503", + "_id": "req_52cadc4a668a4cd58157a434730da4da", + "parentId": "fld_0fe6ae4d2f4e4ce4b370fd2792670219", "modified": 1702991288793, "created": 1680682418257, "url": "{{IRS_HOST}}/irs/jobs", @@ -2084,8 +2084,8 @@ "_type": "request" }, { - "_id": "req_6e01d4795fcd4ec4be9ca7fdc6922fa8", - "parentId": "fld_a78746c77e5042fe9a16e9c07c428503", + "_id": "req_2dbc0c6f7cac4e3d990c8f684045d6d2", + "parentId": "fld_0fe6ae4d2f4e4ce4b370fd2792670219", "modified": 1702991284435, "created": 1680682418247, "url": "{{IRS_HOST}}/irs/jobs", @@ -2139,8 +2139,8 @@ "_type": "request" }, { - "_id": "req_78f9f14b26df47858339421fa25b4692", - "parentId": "fld_a78746c77e5042fe9a16e9c07c428503", + "_id": "req_b338f7d3e6ea444ca473353ba38e7bfd", + "parentId": "fld_0fe6ae4d2f4e4ce4b370fd2792670219", "modified": 1703236659047, "created": 1690384427379, "url": "{{IRS_HOST}}/irs/jobs/{% prompt 'Job ID', '', '', '', false, true %}", @@ -2180,8 +2180,8 @@ "_type": "request" }, { - "_id": "req_ed05f65eded245b88f2e0e8d345c3aea", - "parentId": "fld_a78746c77e5042fe9a16e9c07c428503", + "_id": "req_b17cbac5c1fb4c039569aaaa7a9a5a4e", + "parentId": "fld_0fe6ae4d2f4e4ce4b370fd2792670219", "modified": 1702991276045, "created": 1680682418229, "url": "{{IRS_HOST}}/irs/jobs/{% prompt 'Job ID', '', '', '', false, true %}", @@ -2215,8 +2215,8 @@ "_type": "request" }, { - "_id": "req_5e18d4a7284c43f5aa60ec7e6123a1c7", - "parentId": "fld_a78746c77e5042fe9a16e9c07c428503", + "_id": "req_f57006fe36314a729d471d4e12f67e7e", + "parentId": "fld_0fe6ae4d2f4e4ce4b370fd2792670219", "modified": 1702991272198, "created": 1682498338739, "url": "{{IRS_HOST}}/irs/aspectmodels", @@ -2250,8 +2250,8 @@ "_type": "request" }, { - "_id": "req_6a09b68cf4244a0cb956df180e5f4c82", - "parentId": "fld_f4db18660b724e548809bade6bfce615", + "_id": "req_f95fff1880e54ba898388804f798f85d", + "parentId": "fld_c51adfe29e5e450eb21effa8f3242d1e", "modified": 1705942066941, "created": 1680682418213, "url": "{{ _.SEMANTIC_HUB_URL }}/hub/api/v1/models", @@ -2293,8 +2293,8 @@ "_type": "request" }, { - "_id": "fld_f4db18660b724e548809bade6bfce615", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_c51adfe29e5e450eb21effa8f3242d1e", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1680682418222, "created": 1680682418222, "name": "Semantics Hub", @@ -2305,8 +2305,8 @@ "_type": "request_group" }, { - "_id": "req_b1a321440a2e4dfe902ecca571dc62f5", - "parentId": "fld_f4db18660b724e548809bade6bfce615", + "_id": "req_ee0b0282d68e47e2b950d8722fb09fd2", + "parentId": "fld_c51adfe29e5e450eb21effa8f3242d1e", "modified": 1705942077015, "created": 1680682418204, "url": "{{ _.SEMANTIC_HUB_URL }}/hub/api/v1/models/urn%3Abamm%3Aio.catenax.serial_part_typization%3A1.0.0%23SerialPartTypization", @@ -2335,8 +2335,8 @@ "_type": "request" }, { - "_id": "req_f7f18c23f9ed4b26ba84b6fa81fd5a30", - "parentId": "fld_f4db18660b724e548809bade6bfce615", + "_id": "req_5144d157e6b14563ac9da40036794278", + "parentId": "fld_c51adfe29e5e450eb21effa8f3242d1e", "modified": 1705942085733, "created": 1680682418192, "url": "{{ _.SEMANTIC_HUB_URL }}/hub/api/v1/models/urn%3Abamm%3Aio.catenax.serial_part_typization%3A1.0.0%23SerialPartTypization/json-schema", @@ -2365,8 +2365,8 @@ "_type": "request" }, { - "_id": "req_76a27e96d5d342e8a7354566da95312a", - "parentId": "fld_91009c5cad114695877a204d8fb6e2d1", + "_id": "req_ae558137943343a6ad929382a8b3a29c", + "parentId": "fld_38ea37e2feb443ecbd02440c7d4dc84c", "modified": 1705942100313, "created": 1680682418174, "url": "{{ _.BPDM_URL }}/v1/api/catena/business-partner/{% prompt 'BPN', '', '', '', false, true %}", @@ -2401,8 +2401,8 @@ "_type": "request" }, { - "_id": "fld_91009c5cad114695877a204d8fb6e2d1", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_38ea37e2feb443ecbd02440c7d4dc84c", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1680682418184, "created": 1680682418184, "name": "Business partner data management", @@ -2413,8 +2413,8 @@ "_type": "request_group" }, { - "_id": "req_281c172641d04743b5f4b916639a0fde", - "parentId": "fld_91e10c4c8a0345d6b3a46c5146c12ceb", + "_id": "req_28227bd00d864bc99f67332ec824083f", + "parentId": "fld_7a89506c3c384d70b9beef7abeefbe55", "modified": 1702991347797, "created": 1680682418157, "url": "{{IRS_HOST}}/esr/esr-statistics/{% prompt 'globalAssetId', 'Provide global asset ID or use default', _.GLOBAL_ASSET_ID, '', false, true %}/{% prompt 'BOM Lifecycle', '', '', '', false, true %}/{% prompt 'Certificate', '', '', '', false, true %}/submodel", @@ -2448,8 +2448,8 @@ "_type": "request" }, { - "_id": "fld_91e10c4c8a0345d6b3a46c5146c12ceb", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_7a89506c3c384d70b9beef7abeefbe55", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1680682418167, "created": 1680682418167, "name": "ESR Spike", @@ -2460,8 +2460,8 @@ "_type": "request_group" }, { - "_id": "req_ec674952c1114bce8fb71ea1ed6d9ef7", - "parentId": "fld_e9093d47fe4445c5b0f7a051e4b0f5ad", + "_id": "req_31c8cd5dd85f4cdc8c3ae3af01b034a0", + "parentId": "fld_47f7596261a24253b451004cd8ba2140", "modified": 1702991361578, "created": 1680682418143, "url": "{{IRS_HOST}}/ess/bpn/investigations", @@ -2498,8 +2498,8 @@ "_type": "request" }, { - "_id": "fld_e9093d47fe4445c5b0f7a051e4b0f5ad", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_47f7596261a24253b451004cd8ba2140", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1680682418151, "created": 1680682418151, "name": "ESS Spike", @@ -2510,8 +2510,8 @@ "_type": "request_group" }, { - "_id": "req_98098b47c80842ee9fa4d8a1c00b90cd", - "parentId": "fld_e9093d47fe4445c5b0f7a051e4b0f5ad", + "_id": "req_143d26188fd341b0aa13f5c4784106f2", + "parentId": "fld_47f7596261a24253b451004cd8ba2140", "modified": 1705942138125, "created": 1680682418134, "url": "{{IRS_HOST}}/ess/bpn/investigations/{% response 'body', 'req_ec674952c1114bce8fb71ea1ed6d9ef7', 'b64::JC5pZA==::46b', 'never', 60 %}", @@ -2545,8 +2545,8 @@ "_type": "request" }, { - "_id": "req_53b8324bea944948ae6156efa74b35a2", - "parentId": "fld_e9093d47fe4445c5b0f7a051e4b0f5ad", + "_id": "req_8a0b5ce9751f470cb590a511ae97fb74", + "parentId": "fld_47f7596261a24253b451004cd8ba2140", "modified": 1702991370481, "created": 1680682418134, "url": "{{IRS_HOST}}/ess/bpn/investigations/{% prompt 'Job ID', '', '', '', false, true %}", @@ -2580,8 +2580,8 @@ "_type": "request" }, { - "_id": "req_92b9561d03a54fe79c34d95a93ba0955", - "parentId": "fld_3112c2de59e04e4ea40e8d82a8844ccc", + "_id": "req_ae50b9e3429249719df0aea840ec9456", + "parentId": "fld_576c196fe0fb4274b90431b1b35ae3a6", "modified": 1702991381651, "created": 1680682418118, "url": "{{IRS_HOST}}/irs/orders", @@ -2618,8 +2618,8 @@ "_type": "request" }, { - "_id": "fld_3112c2de59e04e4ea40e8d82a8844ccc", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_576c196fe0fb4274b90431b1b35ae3a6", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1680682418128, "created": 1680682418128, "name": "Batch Processing", @@ -2630,8 +2630,8 @@ "_type": "request_group" }, { - "_id": "req_7b406e4b799c4c06b26b8989cccdfca9", - "parentId": "fld_3112c2de59e04e4ea40e8d82a8844ccc", + "_id": "req_76bbe66f75cd4deab0452c7e2b281305", + "parentId": "fld_576c196fe0fb4274b90431b1b35ae3a6", "modified": 1702991390349, "created": 1696342619602, "url": "{{IRS_HOST}}/irs/ess/orders", @@ -2668,8 +2668,8 @@ "_type": "request" }, { - "_id": "req_78db20c6fbec4ef4a64a15f2b28fe8b9", - "parentId": "fld_3112c2de59e04e4ea40e8d82a8844ccc", + "_id": "req_e5d03176a6f84e7ba6f9e0a293497c0c", + "parentId": "fld_576c196fe0fb4274b90431b1b35ae3a6", "modified": 1705006936944, "created": 1705006139836, "url": "{{IRS_HOST}}/irs/orders/{% prompt 'Order ID', '', '', '', false, true %}", @@ -2707,8 +2707,8 @@ "_type": "request" }, { - "_id": "req_963d3326f7554d41b6730a3b5f293e21", - "parentId": "fld_3112c2de59e04e4ea40e8d82a8844ccc", + "_id": "req_5f370d022e0b422b967d6c80c8a91e2d", + "parentId": "fld_576c196fe0fb4274b90431b1b35ae3a6", "modified": 1702991398473, "created": 1680682418109, "url": "{{IRS_HOST}}/irs/orders/{% prompt 'Order ID', '', '', '', false, true %}", @@ -2742,8 +2742,8 @@ "_type": "request" }, { - "_id": "req_0b2b42f0de3d445eb2dfebe7338cba2f", - "parentId": "fld_3112c2de59e04e4ea40e8d82a8844ccc", + "_id": "req_cd0d65ae1507433891eb7d2a05d5d366", + "parentId": "fld_576c196fe0fb4274b90431b1b35ae3a6", "modified": 1702991409664, "created": 1680682418099, "url": "{{IRS_HOST}}/irs/orders/{% prompt 'Order ID', '', '', '', false, true %}/batches/{% prompt 'Batch ID', '', '', '', false, true %}", @@ -2777,8 +2777,8 @@ "_type": "request" }, { - "_id": "req_c94c97ae6a724112b590952b9a7a0dea", - "parentId": "fld_d5e9b62b127847188e1e4b518720a102", + "_id": "req_5e7e778b5e6543fcb9eea7b99073c93e", + "parentId": "fld_05b3542403444219baacd303af0aee7d", "modified": 1690472186478, "created": 1678358655308, "url": "{{ _.CONSUMER_CONTROLPLANE }}/management/v2/catalog/request", @@ -2815,8 +2815,8 @@ "_type": "request" }, { - "_id": "fld_d5e9b62b127847188e1e4b518720a102", - "parentId": "fld_0839b47b8e964caa9174f954735ab106", + "_id": "fld_05b3542403444219baacd303af0aee7d", + "parentId": "fld_ea70da20cb354243839c8f775fe3bd85", "modified": 1690362660167, "created": 1690362660167, "name": "Catalog", @@ -2827,8 +2827,8 @@ "_type": "request_group" }, { - "_id": "fld_0839b47b8e964caa9174f954735ab106", - "parentId": "fld_1810e742cdde46ab9d50b692c5b8da98", + "_id": "fld_ea70da20cb354243839c8f775fe3bd85", + "parentId": "fld_7ea21da81d7f43b59b1eadd5558a0d4e", "modified": 1690363778601, "created": 1675675609576, "name": "EDC-Requests", @@ -2839,8 +2839,8 @@ "_type": "request_group" }, { - "_id": "req_13e43b7f93484f80b8006c04b582e128", - "parentId": "fld_d5e9b62b127847188e1e4b518720a102", + "_id": "req_d662c03a94f446d2afc6d8ada83bfe3e", + "parentId": "fld_05b3542403444219baacd303af0aee7d", "modified": 1691500654267, "created": 1685521485278, "url": "{{ _.CONSUMER_CONTROLPLANE }}/management/v2/catalog/request", @@ -2877,8 +2877,8 @@ "_type": "request" }, { - "_id": "req_d10e25e171d44f0581305ceec28c90bc", - "parentId": "fld_d5e9b62b127847188e1e4b518720a102", + "_id": "req_c32a0a83b43d494e9cbffdfe035badec", + "parentId": "fld_05b3542403444219baacd303af0aee7d", "modified": 1705940987109, "created": 1691654388376, "url": "{{ _.CONSUMER_CONTROLPLANE }}/management/v2/catalog/request", @@ -2915,8 +2915,8 @@ "_type": "request" }, { - "_id": "req_080fffcbca8246cca56c1b89e2ee87d7", - "parentId": "fld_1e28df04607146aa953989fb9b878c79", + "_id": "req_f745598008ad40808cce24fa009d9c4d", + "parentId": "fld_d6f7d906c7fd4203b8c75da059900aa9", "modified": 1691578280640, "created": 1675675609557, "url": "{{ _.CONSUMER_CONTROLPLANE }}/management/v2/contractnegotiations", @@ -2953,8 +2953,8 @@ "_type": "request" }, { - "_id": "fld_1e28df04607146aa953989fb9b878c79", - "parentId": "fld_0839b47b8e964caa9174f954735ab106", + "_id": "fld_d6f7d906c7fd4203b8c75da059900aa9", + "parentId": "fld_ea70da20cb354243839c8f775fe3bd85", "modified": 1684146626847, "created": 1684146519491, "name": "Negotiation", @@ -2965,8 +2965,8 @@ "_type": "request_group" }, { - "_id": "req_4b81e1450a7044f4b495396a3c6beec5", - "parentId": "fld_1e28df04607146aa953989fb9b878c79", + "_id": "req_3f181fd162664bb89604fd311628d84e", + "parentId": "fld_d6f7d906c7fd4203b8c75da059900aa9", "modified": 1690362123962, "created": 1675675609549, "url": "{{ _.CONSUMER_CONTROLPLANE }}/management/v2/contractnegotiations/{% prompt 'id', '', '', '', false, true %}", @@ -3000,8 +3000,8 @@ "_type": "request" }, { - "_id": "req_941858a9157241bdb4b143f49461378e", - "parentId": "fld_1e28df04607146aa953989fb9b878c79", + "_id": "req_41e49b80380f401ca7ba1c3079e36acf", + "parentId": "fld_d6f7d906c7fd4203b8c75da059900aa9", "modified": 1690362117725, "created": 1685444139708, "url": "{{ _.CONSUMER_CONTROLPLANE }}/management/v2/contractnegotiations/{% prompt 'id', '', '', '', false, true %}/cancel", @@ -3030,8 +3030,8 @@ "_type": "request" }, { - "_id": "req_d321712166e0404798f4c201b8928aba", - "parentId": "fld_1e28df04607146aa953989fb9b878c79", + "_id": "req_dd017a01aaef4e6aa43a006a7116cbd2", + "parentId": "fld_d6f7d906c7fd4203b8c75da059900aa9", "modified": 1690361721223, "created": 1681911985730, "url": "{{ _.CONSUMER_CONTROLPLANE }}/management/v2/contractnegotiations/request", @@ -3068,8 +3068,8 @@ "_type": "request" }, { - "_id": "req_4ef3b469eec84623ba6fb1558baf732d", - "parentId": "fld_1e28df04607146aa953989fb9b878c79", + "_id": "req_8315d1cf3016462ebaf36b221ff121b7", + "parentId": "fld_d6f7d906c7fd4203b8c75da059900aa9", "modified": 1691578311420, "created": 1675675609541, "url": "{{ _.CONSUMER_CONTROLPLANE }}/management/v2/transferprocesses", @@ -3106,8 +3106,8 @@ "_type": "request" }, { - "_id": "req_e86d72124f744590931f5cdf6d4ba618", - "parentId": "fld_1e28df04607146aa953989fb9b878c79", + "_id": "req_475b5dc25a0349a0bdaf1388767009d3", + "parentId": "fld_d6f7d906c7fd4203b8c75da059900aa9", "modified": 1691503370103, "created": 1679993996270, "url": "{{ _.CONSUMER_CONTROLPLANE }}/management/v2/transferprocesses/{% prompt 'id', '', '', '', false, true %}", @@ -3141,8 +3141,8 @@ "_type": "request" }, { - "_id": "req_a4cd5461e45d44d1b0ce29c5076dd8ab", - "parentId": "fld_1e28df04607146aa953989fb9b878c79", + "_id": "req_b7d4d2ae87f848d18e09fa9066a64def", + "parentId": "fld_d6f7d906c7fd4203b8c75da059900aa9", "modified": 1690361795179, "created": 1675675609525, "url": "{{ _.CONSUMER_CONTROLPLANE }}/management/v2/transferprocesses/request", @@ -3179,8 +3179,8 @@ "_type": "request" }, { - "_id": "req_c13b0227ea6649c185319bddf09e1ff4", - "parentId": "fld_1e28df04607146aa953989fb9b878c79", + "_id": "req_07bb49740fd6490d960262fe773ca273", + "parentId": "fld_d6f7d906c7fd4203b8c75da059900aa9", "modified": 1690361763512, "created": 1681910653593, "url": "{{CONSUMER_CONTROLPLANE}}/management/v2/contractagreements/request", @@ -3217,8 +3217,8 @@ "_type": "request" }, { - "_id": "req_cd795997401045719846198f474ac6c4", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_9c20ac607a1f4dd2ac6fa7d44464ee39", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690362947546, "created": 1681907482278, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/assets", @@ -3255,8 +3255,8 @@ "_type": "request" }, { - "_id": "fld_b72920a81fc94ba7ac254f87c04e4bdc", - "parentId": "fld_0839b47b8e964caa9174f954735ab106", + "_id": "fld_cdf7808307974213bddcaa8c454ebfa4", + "parentId": "fld_ea70da20cb354243839c8f775fe3bd85", "modified": 1705940929752, "created": 1684146457388, "name": "Provider", @@ -3267,8 +3267,8 @@ "_type": "request_group" }, { - "_id": "req_1b5a18e8940948b3b0593152919f0f98", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_491fb44312cc49138ce948dffd4342d5", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690362407763, "created": 1685444139630, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/assets/{% prompt 'id', '', '', '', false, true %}", @@ -3297,8 +3297,8 @@ "_type": "request" }, { - "_id": "req_b00faefd0be84da8972cfe82c018103f", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_244dee9fd5a64fafa2f5ac5bdaa6994d", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690362438115, "created": 1685444139625, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/assets/request", @@ -3335,8 +3335,8 @@ "_type": "request" }, { - "_id": "req_7705a8d515234eeda8a00b72b33c0810", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_27e29ff2819d489ea9248fab518be562", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690362400081, "created": 1685444139636, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/assets/{% prompt 'id', '', '', '', false, true %}", @@ -3365,8 +3365,8 @@ "_type": "request" }, { - "_id": "req_b3df4c26f45442b0969e2601ba943f91", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_605ebafb36034d11b783f2c20fe0e764", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690362581978, "created": 1685444139641, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/policydefinitions", @@ -3403,8 +3403,8 @@ "_type": "request" }, { - "_id": "req_cccc83c71e834cac9e1e7e1562930645", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_3d746a9c3a52496499bf5bfac31acbad", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690362549290, "created": 1685444139647, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/policydefinitions/{% prompt 'id', '', '', '', false, true %}", @@ -3433,8 +3433,8 @@ "_type": "request" }, { - "_id": "req_b628dd4600654a57a3d1c4abd71f032f", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_15a39c3c4029494ca53867d6a3c28a33", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690362591799, "created": 1685444139653, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/policydefinitions/request", @@ -3471,8 +3471,8 @@ "_type": "request" }, { - "_id": "req_c2c69580f2014ea0af92c45b3efa1a0a", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_487d7bd02af746db8c6cda9d0f4d662a", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690362559324, "created": 1685444139659, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/policydefinitions/{% prompt 'id', '', '', '', false, true %}", @@ -3501,8 +3501,8 @@ "_type": "request" }, { - "_id": "req_9de3020e595e4a79ade1de9bbb5645bd", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_324212ad1f21447880f16ad152d861ab", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690363080444, "created": 1685444139665, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/contractdefinitions", @@ -3539,8 +3539,8 @@ "_type": "request" }, { - "_id": "req_30779ac0b772473094ad1ab750784f72", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_d90aecacb8e446c795a6b0fc8e007e90", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690362691392, "created": 1685444139672, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/contractdefinitions/{% prompt 'id', '', '', '', false, true %}", @@ -3569,8 +3569,8 @@ "_type": "request" }, { - "_id": "req_30af5f472981473c9cfec7fe1cceacd6", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_d0cc3341bce24e3aa7459b319755e918", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690363126919, "created": 1685444139678, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/contractdefinitions/request", @@ -3607,8 +3607,8 @@ "_type": "request" }, { - "_id": "req_dbc8413f51774f0bab4afdbee92b9b21", - "parentId": "fld_b72920a81fc94ba7ac254f87c04e4bdc", + "_id": "req_13b593535b3e46f2904fd938a830bd66", + "parentId": "fld_cdf7808307974213bddcaa8c454ebfa4", "modified": 1690363136216, "created": 1685444139684, "url": "{{ _.PROVIDER_CONTROLPLANE_1 }}/management/v2/contractdefinitions/{% prompt 'id', '', '', '', false, true %}", @@ -3645,8 +3645,8 @@ "_type": "request" }, { - "_id": "env_d2b7eb1621841465ea24b73343568b286aa8ac9a", - "parentId": "wrk_565df8abe30f4da29d8bffcde97927d7", + "_id": "env_0bce3b8371da4128ab2299d44b2e5324", + "parentId": "wrk_dbeadb1e56e942299e6c920265e6c667", "modified": 1683638503332, "created": 1680782486844, "name": "Base Environment", @@ -3658,8 +3658,8 @@ "_type": "environment" }, { - "_id": "jar_d2b7eb1621841465ea24b73343568b286aa8ac9a", - "parentId": "wrk_565df8abe30f4da29d8bffcde97927d7", + "_id": "jar_dbc637bc345d403a8f7dd19f333a3bde", + "parentId": "wrk_dbeadb1e56e942299e6c920265e6c667", "modified": 1705938127468, "created": 1680782486851, "name": "Default Jar", @@ -3788,16 +3788,6 @@ } ], "_type": "cookie_jar" - }, - { - "_id": "spc_22dfe33611af4731965cc2b08febcfdb", - "parentId": "wrk_565df8abe30f4da29d8bffcde97927d7", - "modified": 1680782484284, - "created": 1680782484284, - "fileName": "IRS", - "contents": "", - "contentType": "yaml", - "_type": "api_spec" } ] } \ No newline at end of file diff --git a/local/testing/api-tests/irs-api-tests.tavern.yaml b/local/testing/api-tests/irs-api-tests.tavern.yaml index c9de2ed681..e76a085bfd 100644 --- a/local/testing/api-tests/irs-api-tests.tavern.yaml +++ b/local/testing/api-tests/irs-api-tests.tavern.yaml @@ -3200,5 +3200,119 @@ stages: status_code: 200 verify_response_with: function: local.testing.api-tests.tavern_helpers:check_batches_are_canceled_correctly + headers: + content-type: application/json + + +--- + + +test_name: Make sure jobs are responsed with contractAgreementIds in shells and submodels if auditContractNegotiation activated + +strict: + - headers:off + - json:off + +stages: + - name: create a job and check for success + request: + url: "{tavern.env_vars.IRS_HOST}/irs/jobs" + json: + key: + globalAssetId: "{tavern.env_vars.GLOBAL_ASSET_ID_AS_BUILT}" + bpn: "{tavern.env_vars.BPN_AS_BUILT}" + collectAspects: true + auditContractNegotiation: true + depth: 2 + aspects: + - SerialPart + direction: "downward" + method: POST + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_api_key + response: + status_code: 201 + headers: + content-type: application/json + save: + json: + job_id: id + + - *verify_job_is_running_and_wait_up_to_15_minutes_for_COMPLETED + + - name: verify job response with desired test steps + request: + url: "{tavern.env_vars.IRS_HOST}/irs/jobs/{job_id}" + params: + returnUncompletedJob: true + method: GET + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_api_key + response: + status_code: 200 + verify_response_with: + - function: local.testing.api-tests.tavern_helpers:contractAgreementId_in_shells_existing + - function: local.testing.api-tests.tavern_helpers:contractAgreementId_in_submodels_existing + headers: + content-type: application/json + + +--- + + +test_name: Make sure jobs are not responsed with contractAgreementIds in shells and submodels if auditContractNegotiation deactivated + +strict: + - headers:off + - json:off + +stages: + - name: create a job and check for success + request: + url: "{tavern.env_vars.IRS_HOST}/irs/jobs" + json: + key: + globalAssetId: "{tavern.env_vars.GLOBAL_ASSET_ID_AS_BUILT}" + bpn: "{tavern.env_vars.BPN_AS_BUILT}" + collectAspects: true + auditContractNegotiation: false + depth: 2 + aspects: + - SerialPart + direction: "downward" + method: POST + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_api_key + response: + status_code: 201 + headers: + content-type: application/json + save: + json: + job_id: id + + - *verify_job_is_running_and_wait_up_to_15_minutes_for_COMPLETED + + - name: verify job response with desired test steps + request: + url: "{tavern.env_vars.IRS_HOST}/irs/jobs/{job_id}" + params: + returnUncompletedJob: true + method: GET + headers: + content-type: application/json + $ext: + function: local.testing.api-tests.tavern_helpers:create_api_key + response: + status_code: 200 + verify_response_with: + - function: local.testing.api-tests.tavern_helpers:contractAgreementId_in_shells_not_existing + - function: local.testing.api-tests.tavern_helpers:contractAgreementId_in_submodels_not_existing headers: content-type: application/json \ No newline at end of file diff --git a/local/testing/api-tests/tavern_helpers.py b/local/testing/api-tests/tavern_helpers.py index 22f7c934d2..50a0a0164e 100644 --- a/local/testing/api-tests/tavern_helpers.py +++ b/local/testing/api-tests/tavern_helpers.py @@ -1,4 +1,3 @@ -# testing_utils.py from datetime import datetime import os @@ -354,4 +353,36 @@ def create_api_key(): def create_api_key_ess(): api_key = os.getenv('ADMIN_USER_API_KEY_ESS') - return {"X-API-KEY": api_key} \ No newline at end of file + return {"X-API-KEY": api_key} + + +def contractAgreementId_in_shells_existing(response): + shells = response.json().get("shells") + print("shells ", shells) + assert len(shells) >= 1 + for i in shells: + assert i.get("contractAgreementId") is not None + + +def contractAgreementId_in_submodels_existing(response): + submodels = response.json().get("submodels") + print("submodels ", submodels) + assert len(submodels) >= 1 + for i in submodels: + assert i.get("contractAgreementId") is not None + + +def contractAgreementId_in_shells_not_existing(response): + shells = response.json().get("shells") + print("shells ", shells) + assert len(shells) >= 1 + for i in shells: + assert i.get("contractAgreementId") is None + + +def contractAgreementId_in_submodels_not_existing(response): + submodels = response.json().get("submodels") + print("submodels ", submodels) + assert len(submodels) >= 1 + for i in submodels: + assert i.get("contractAgreementId") is None diff --git a/pom.xml b/pom.xml index 2e47b76a2d..8be9e7d052 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ - 1.5.1-SNAPSHOT + 1.5.2-SNAPSHOT 3.1.8 @@ -94,7 +94,7 @@ 0.2.1 3.5.0 1.76 - 3.2.0 + 3.3.1 1.16.1 0.12.0 2.14.0 @@ -121,7 +121,7 @@ 4.3.0 3.3.0 3.1.0 - 1.0.3-SNAPSHOT + 1.1.0 1.1.10.5 @@ -158,10 +158,10 @@ - dash-licenses-snapshots - https://repo.eclipse.org/content/repositories/dash-licenses-snapshots/ + dash-licenses-releases + https://repo.eclipse.org/content/repositories/dash-licenses-releases/ - true + false