diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e5834a9c..33ec8a983e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - POST /management/v2/contractagreements/request and GET /management/v2/contractagreements/{contractAgreementId}/negotiation to irs-edc-client lib ### Changed +- EDC client handles multiple Digital Twin Registries and Digital Twins now #395 - Change logo of irs - Added 'businessPartnerNumber' field to Tombstone model. This will be filled only when UsagePolicyValidation tombstone is being created. 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 deleted file mode 100644 index 0689cd80a5..0000000000 --- a/docs/src/docs/arc42/cross-cutting/discovery-DTR--EDC-with-multiple-DTRs.puml +++ /dev/null @@ -1,28 +0,0 @@ -@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 index 5956218ecc..dfa8181f1d 100644 --- 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 @@ -1,5 +1,5 @@ @startuml -actor IRS +participant IRS participant DTR IRS -> DTR: /query for globalAssetId diff --git a/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-multiple-DTRs--detailed.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-multiple-DTRs--detailed.puml new file mode 100644 index 0000000000..c0d6659f8c --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-multiple-DTRs--detailed.puml @@ -0,0 +1,108 @@ +@startuml + + +box IRS + participant DecentralDigitalTwinRegistryService as DTRS + participant EdcSubmodelClientImpl as EdcClient +end box + + +participant DiscoveryService +participant "EDC Provider 1" as EDCProvider1 +participant "EDC Provider 2" as EDCProvider2 +participant "EDC Provider 3" as EDCProvider3 +participant "DTR1" as DTR1 +participant "DTR2" as DTR2 + +' DigitalTwinDelegate.process +' DecentralDigitalTwinRegistryService.fetchShells +' ConnectorEndpointsService.fetchConnectorEndpoints(String bpn) -- cacheable +DTRS -> DiscoveryService: Get EDCs for BPN +' discoveryFinderClient.findDiscoveryEndpoints(DiscoveryFinderRequest request) +DiscoveryService -> DTRS: Return list of 3 EDCs + +' Turned into futures to get the EDR tokens by +' EndpointDataForConnectorsService.createFindEndpointDataForConnectorsFutures(List edcUrls) + +par + group CatalogRequestEDC1 + + == EDC Control Plane == + DTRS -> EdcClient: Get EDR Token for asset + EdcClient -> EDCProvider1: Query for DTR1 contract offer + EDCProvider1 -> EdcClient: No offer + EdcClient -> DTRS: No token + end + + else + + group CatalogRequestEDC2 DTR1 + + == EDC Control Plane == + + ' this happens in DecentralDigitalTwinRegistryService.fetchShellDescriptorsForConnectorEndpoints + ' when each of List> is composed + ' with fetchShellDescriptorsForKey + DTRS -> EdcClient: Get EDR Token for asset + ' EdcSubmodelFacade.getEndpointReferenceForAsset + ' EdcSubmodelClientImpl.getEndpointReferenceForAsset + EdcClient -> EDCProvider2: Query for DTR1 contract offer + EDCProvider2 -> EdcClient: DTR1 contract offer + ' EdcSubmodelClientImpl.negotiateContract + EdcClient -> EDCProvider2: Negotiate contract + EDCProvider2 -> EdcClient: EDR Token callback + + EdcClient -> DTRS: EDR token + + + == EDC Data Plane == + + ' DecentralDigitalTwinRegistryService + ' .fetchShellDescriptor(EndpointDataReference, DigitalTwinRegistryKey) + DTRS -> DTR1: Query for DT + DTR1 -> DTRS: DT + ' result is AssetAdministrationShellDescriptor which is the actual data + end + + else + + group CatalogRequestEDC2 DTR2 + + == EDC Control Plane == + + DTRS -> EdcClient: Get EDR Token for asset + EdcClient -> EDCProvider2: Query for DTR1 contract offer + EDCProvider2 -> EdcClient: DTR1 contract offer + EdcClient -> EDCProvider2: Negotiate contract + EDCProvider2 -> EdcClient: EDR Token callback + + EdcClient -> DTRS: EDR token + + + == EDC Data Plane == + + DTRS -> DTR2: Query for DT + DTR2 -> DTRS: DT + end + + else + + group CatalogRequestEDC3 + + == EDC Control Plane == + + DTRS -> EdcClient: Get EDR Token for asset + EdcClient -> EDCProvider3: Query for DTR1 contract offer + EDCProvider3 -> EdcClient: DTR1 contract offer + EdcClient -> EDCProvider3: Negotiate contract + EDCProvider3 -> EdcClient: EDR Token callback + EdcClient -> DTRS: EDR token + + == EDC Data Plane == + + DTRS -> DTR1: Query for DT + DTR1 -> DTRS: No DT + end +end + +@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 index be20fd8964..4e75115760 100644 --- 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 @@ -6,33 +6,40 @@ 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 +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 + ' == EDC Control Plane == + 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 + ' == EDC Control Plane == + IRS -> EDCProvider2: Query for DTR contract offer + EDCProvider2 -> IRS: DTR contract offer IRS -> EDCProvider2: Negotiate contract - IRS ->> DTR: Query for DT - DTR ->> IRS: DT + + '== EDC Data Plane == + IRS -> DTR: Query for DT + DTR -> IRS: DT end else group CatalogRequestEDC3 - IRS ->> EDCProvider3: Query for DTR contract offer - EDCProvider3 ->> IRS: DTR contract offer + '== EDC Control Plane == + 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 + + '== EDC Data Plane == + 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--detailed.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-no-DTRs--detailed.puml new file mode 100644 index 0000000000..53def896d5 --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-no-DTRs--detailed.puml @@ -0,0 +1,58 @@ +@startuml + +box IRS + participant DecentralDigitalTwinRegistryService as DTRS + participant EdcSubmodelClientImpl as EdcClient +end box + +participant "Discovery Service" as DiscoveryService + +participant "EDC 1" as EDCProvider1 +participant "EDC 2" as EDCProvider2 +participant "EDC 3" as EDCProvider3 + + +' ConnectorEndpointsService.fetchConnectorEndpoints(String bpn) -- cacheable +DTRS -> DiscoveryService: Get EDCs for BPN +' discoveryFinderClient.findDiscoveryEndpoints(DiscoveryFinderRequest request) +DiscoveryService -> DTRS: Return list of 3 EDCs + +par + group Catalog Request to EDC 1 + + == EDC Control Plane == + + DTRS -> EdcClient: Get EDR Token for asset + EdcClient -> EDCProvider1: Query for DTR contract offer + EDCProvider1 -> EdcClient: No offer + EdcClient -> DTRS: No token + end + + else + + group Catalog Request to EDC 2 + + == EDC Control Plane == + + DTRS -> EdcClient: Get EDR Token for asset + EdcClient -> EDCProvider2: Query for DTR contract offer + EDCProvider2 -> EdcClient: No offer + EdcClient -> DTRS: No token + end + + else + + group Catalog Request to EDC 3 + + == EDC Control Plane == + + DTRS -> EdcClient: Get EDR Token for asset + EdcClient -> EDCProvider3: Query for DTR contract offer + EDCProvider3 -> EdcClient: No offer + EdcClient -> DTRS: No token + end +end + +DTRS -> DTRS: Tombstone + +@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 index aaed9497b2..0735db0bd8 100644 --- 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 @@ -1,6 +1,6 @@ @startuml -actor IRS -actor "Discovery Service" as DiscoveryService +participant IRS +participant "Discovery Service" as DiscoveryService participant "EDC 1" as EDCProvider1 participant "EDC 2" as EDCProvider2 @@ -11,6 +11,7 @@ DiscoveryService -> IRS: Return list of 3 EDCs par group Catalog Request to EDC 1 + '== EDC Control Plane == IRS -> EDCProvider1: Query for DTR contract offer EDCProvider1 -> IRS: No offer end @@ -18,6 +19,7 @@ par else group Catalog Request to EDC 2 + '== EDC Control Plane == IRS -> EDCProvider2: Query for DTR contract offer EDCProvider2 -> IRS: No offer end @@ -25,6 +27,7 @@ par else group Catalog Request to EDC 3 + '== EDC Control Plane == IRS -> EDCProvider3: Query for DTR contract offer EDCProvider3 -> IRS: No offer end diff --git a/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-one-DTR--detailed.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-one-DTR--detailed.puml new file mode 100644 index 0000000000..d2a2a9ca4d --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-one-DTR--detailed.puml @@ -0,0 +1,61 @@ +@startuml + +box IRS + participant DecentralDigitalTwinRegistryService as DTRS + participant EdcSubmodelClientImpl as EdcClient +end box + +participant DiscoveryService +participant "EDC Provider 1" as EDCProvider1 +participant "EDC Provider 2" as EDCProvider2 +participant "EDC Provider 3" as EDCProvider3 +participant DTR + +' ConnectorEndpointsService.fetchConnectorEndpoints(String bpn) -- cacheable +DTRS -> DiscoveryService: Get EDCs for BPN +' discoveryFinderClient.findDiscoveryEndpoints(DiscoveryFinderRequest request) +DiscoveryService -> DTRS: Return list of 3 EDCs + +par + group CatalogRequestEDC1 + + == EDC Control Plane == + + DTRS -> EdcClient: Get EDR Token for asset + EdcClient -> EDCProvider1: Query for DTR contract offer + EDCProvider1 -> EdcClient: No offer + EdcClient -> DTRS: No token + end + + else + + group CatalogRequestEDC2 + + == EDC Control Plane == + + DTRS -> EdcClient: Get EDR Token for asset + EdcClient -> EDCProvider2: Query for DTR contract offer + EDCProvider2 -> EdcClient: No offer + EdcClient -> DTRS: No token + end + + else + + group CatalogRequestEDC3 + + == EDC Control Plane == + + DTRS -> EdcClient: Get EDR Token for asset + EdcClient -> EDCProvider3: Query for DTR contract offer + EDCProvider3 -> EdcClient: DTR contract offer + EdcClient -> EDCProvider3: Negotiate contract + EDCProvider3 -> EdcClient: EDR Token callback + EdcClient -> DTRS: EDR token + + == EDC Data Plane == + + DTRS -> DTR: Query for DT + DTR -> DTRS: DT + end +end +@enduml \ No newline at end of file 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 index f70cc4ac49..d7f090ffee 100644 --- 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 @@ -6,30 +6,35 @@ 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 +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 + '== EDC Control Plane == + 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 + '== EDC Control Plane == + 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 + '== EDC Control Plane == + IRS -> EDCProvider3: Query for DTR contract offer + EDCProvider3 -> IRS: DTR contract offer IRS -> EDCProvider3: Negotiate contract - IRS ->> DTR: Query for DT - DTR ->> IRS: DT + + '== EDC Data Plane == + 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-multiple-DTRs--detailed.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--one-EDC-with-multiple-DTRs--detailed.puml new file mode 100644 index 0000000000..353e2db879 --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--one-EDC-with-multiple-DTRs--detailed.puml @@ -0,0 +1,52 @@ +@startuml + +box IRS + participant DecentralDigitalTwinRegistryService as DTRS + participant EdcSubmodelClientImpl as EdcClient +end box + +participant DiscoveryService +participant "EDC Provider" as EDCProvider +participant "DTR 1" as DTR1 +participant "DTR 2" as DTR2 + +' ConnectorEndpointsService.fetchConnectorEndpoints(String bpn) -- cacheable +DTRS -> DiscoveryService: Get EDCs for BPN +' discoveryFinderClient.findDiscoveryEndpoints(DiscoveryFinderRequest request) +DiscoveryService -> DTRS: Return list of 1 EDC + +== EDC Control Plane == + +' see EdcSubmodelFacade.getEndpointReferenceForAsset, +' EdcSubmodelClient.getEndpointReferencesForAsset, +' EdcSubmodelClientImpl.getEndpointReferencesForAsset +DTRS -> EdcClient: Get EDR Token for asset +EdcClient ->> EDCProvider: Query for DTR contract offer +EDCProvider -> EdcClient: 2 DTR contract offers + +par + group Query DTR 1 + EdcClient -> EDCProvider: Negotiate contract + EDCProvider -> EdcClient: EDR Token callback + EdcClient -> DTRS: EDR token + + == EDC Data Plane == + + DTRS -> DTR1: Query for DT + DTR1 -> DTRS: no DT + end + + else + + group Query DTR 2 + EdcClient -> EDCProvider: Negotiate contract + EDCProvider -> EdcClient: EDR Token callback + EdcClient -> DTRS: EDR token + + == EDC Data Plane == + + DTRS -> DTR2: Query for DT + DTR2 -> DTRS: DT + end +end +@enduml \ No newline at end of file diff --git a/docs/src/docs/arc42/cross-cutting/discovery-DTR--one-EDC-with-multiple-DTRs.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--one-EDC-with-multiple-DTRs.puml new file mode 100644 index 0000000000..b5765c0a7b --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--one-EDC-with-multiple-DTRs.puml @@ -0,0 +1,29 @@ +@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--one-EDC-with-one-DTR--detailed.puml b/docs/src/docs/arc42/cross-cutting/discovery-DTR--one-EDC-with-one-DTR--detailed.puml new file mode 100644 index 0000000000..29ea1509b6 --- /dev/null +++ b/docs/src/docs/arc42/cross-cutting/discovery-DTR--one-EDC-with-one-DTR--detailed.puml @@ -0,0 +1,41 @@ +@startuml + +box IRS + participant DecentralDigitalTwinRegistryService as DTRS + participant EdcSubmodelClientImpl as EdcClient +end box + +participant DiscoveryService +participant "EDC Provider 3" as EDCProvider3 +participant DTR + +' DigitalTwinDelegate.process +' ConnectorEndpointsService.fetchConnectorEndpoints(String bpn) -- cacheable +DTRS -> DiscoveryService: Get EDCs for BPN +' discoveryFinderClient.findDiscoveryEndpoints(DiscoveryFinderRequest request) +DiscoveryService -> DTRS: Return list of 1 EDC + +== EDC Control Plane == + +' see EdcSubmodelFacade.getEndpointReferenceForAsset, +' EdcSubmodelClient.getEndpointReferencesForAsset, +' EdcSubmodelClientImpl.getEndpointReferencesForAsset +DTRS -> EdcClient: Get EDR Token for asset +EdcClient -> EDCProvider3: Query for DTR contract offer +EDCProvider3 -> EdcClient: DTR contract offer +' Contract offer = CatalogItem +EdcClient -> EDCProvider3: Negotiate contract + +EDCProvider3 -> EdcClient: EDR Token callback +' EDR Token callback (this is the answer from pollingService) +EdcClient -> DTRS: EDR Token +' EDR Token = EndpointDataReference + +== EDC Data Plane == + +' mapToShellId +' DecentralDigitalTwinRegistryClient.getAssetAdministrationShellDescriptor +DTRS -> DTR: Query for DT +DTR -> DTRS: DT + +@enduml \ No newline at end of file diff --git a/docs/src/docs/arc42/cross-cutting/discovery-process-dtr.adoc b/docs/src/docs/arc42/cross-cutting/discovery-process-dtr.adoc index 10cea2a582..40c77d810e 100644 --- a/docs/src/docs/arc42/cross-cutting/discovery-process-dtr.adoc +++ b/docs/src/docs/arc42/cross-cutting/discovery-process-dtr.adoc @@ -1,18 +1,25 @@ 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 +==== Scenario 1: One 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] +[plantuml,target=discovery-DTR--one-EDC-with-multiple-DTRs,format=svg] +.... +include::discovery-DTR--one-EDC-with-multiple-DTRs.puml[] +.... + + +Same diagram with a little more detail on IRS side: + +[plantuml,target=discovery-DTR--one-EDC-with-multiple-DTRs--detailed,format=svg] .... -include::discovery-DTR--EDC-with-multiple-DTRs.puml[] +include::discovery-DTR--one-EDC-with-multiple-DTRs--detailed.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. @@ -23,6 +30,16 @@ The first registry which responds with a DT will be the one used by IRS. include::discovery-DTR--multiple-EDCs-with-one-DTR.puml[] .... + +Same diagram with a little more detail on IRS side: + +[plantuml,target=discovery-DTR--multiple-EDCs-with-one-DTR--detailed,format=svg] +.... +include::discovery-DTR--multiple-EDCs-with-one-DTR--detailed.puml[] +.... + + + ==== Scenario 3: One EDC with one DTR Only one EDC found for BPN and the catalog only contains one offer for the DTR. @@ -33,6 +50,14 @@ IRS will use this registry and will create a tombstone if no DT could be found f include::discovery-DTR--one-EDC-with-one-DTR.puml[] .... +Same diagram with a little more detail on IRS side: + +[plantuml,target=discovery-DTR--one-EDC-with-one-DTR--detailed,format=svg] +.... +include::discovery-DTR--one-EDC-with-one-DTR--detailed.puml[] +.... + + ==== Scenario 4: Multiple EDCs with multiple DTRs IRS starts a contract negotiation for all the registry offers. @@ -42,6 +67,15 @@ IRS starts a contract negotiation for all the registry offers. include::discovery-DTR--multiple-EDCs-with-multiple-DTRs.puml[] .... +Same diagram with a little more detail on IRS side: + +[plantuml,target=discovery-DTR--multiple-EDCs-with-multiple-DTRs--detailed,format=svg] +.... +include::discovery-DTR--multiple-EDCs-with-multiple-DTRs--detailed.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. @@ -51,6 +85,13 @@ IRS starts a contract negotiation for all the registry offers and creates a tomb include::discovery-DTR--multiple-EDCs-with-no-DTRs.puml[] .... +Same diagram with a little more detail on IRS side: + +[plantuml,target=discovery-DTR--multiple-EDCs-with-no-DTRs--detailed,format=svg] +.... +include::discovery-DTR--multiple-EDCs-with-no-DTRs--detailed.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. diff --git a/docs/src/docs/arc42/glossary.adoc b/docs/src/docs/arc42/glossary.adoc index dbdb67a492..b9e206f7b9 100644 --- a/docs/src/docs/arc42/glossary.adoc +++ b/docs/src/docs/arc42/glossary.adoc @@ -3,13 +3,15 @@ |=== |Term |Description -|AAS | Asset Administration Shell (Industry 4.0) +|Asset Administration Shell (AAS) | see "Digital Twin" |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 +|CatalogItem| A "CatalogItem" from EDC is a synonym for "Contract Offer". +|Contract Offer| A "Contract Offer" is a synonym for "CatalogItem" from EDC. |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). +|Digital Twin (DT) | The Digital Twin is the key technology of Industry 4.0 and connects the physical world with the digital world and acts as an enabler of the Catena-X network. It is based on a standardized programming interface of the Industrial https://industrialdigitaltwin.org/[Digital Twin Association (IDTA)] and its Asset Administration Shell. +|Digital Twin Registry (DTR) | 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 @@ -27,6 +29,7 @@ https://github.com/eclipse-tractusx/ssi-docu/blob/main/docs/architecture/cx-3-2/ |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] +|Shell | see "Asset Administration Shell" |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 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 06c655afa6..5601a2f3ad 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 @@ -68,17 +68,19 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai } try { - final Shell shell = digitalTwinRegistryService.fetchShells(List.of(new DigitalTwinRegistryKey(itemId.getGlobalAssetId(), itemId.getBpn()))) - .stream() - .findFirst() - .orElseThrow(); + final var dtrKeys = List.of(new DigitalTwinRegistryKey(itemId.getGlobalAssetId(), itemId.getBpn())); + final Shell shell = digitalTwinRegistryService.fetchShells(dtrKeys).stream() + // we use findFirst here, because we query only for one + // DigitalTwinRegistryKey here + .findFirst().orElseThrow(); if (!expectedDepthOfTreeIsNotReached(jobData.getDepth(), aasTransferProcess.getDepth())) { // filter submodel descriptors if next delegate will not be executed shell.payload().withFilteredSubmodelDescriptors(jobData.getAspects()); } - itemContainerBuilder.shell(jobData.isAuditContractNegotiation() ? shell : shell.withoutContractAgreementId()); + 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 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 14dd51dbef..d042554283 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 @@ -77,7 +77,8 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai .getShells() .stream() .findFirst() - .ifPresent(shell -> shell.payload().findRelationshipEndpointAddresses( + .ifPresent(shell -> shell.payload() + .findRelationshipEndpointAddresses( AspectType.fromValue(relationshipAspect.getName())) .forEach(endpoint -> processEndpoint(endpoint, relationshipAspect, aasTransferProcess, itemContainerBuilder, itemId))); @@ -99,8 +100,8 @@ private void processEndpoint(final Endpoint endpoint, final RelationshipAspect r } try { - final String submodelRawPayload = requestSubmodel(submodelFacade, connectorEndpointsService, - endpoint, itemId.getBpn()).getPayload(); + final String submodelRawPayload = requestSubmodel(submodelFacade, connectorEndpointsService, endpoint, + itemId.getBpn()).getPayload(); final var relationships = jsonUtil.fromString(submodelRawPayload, relationshipAspect.getSubmodelClazz()) .asRelationships(); @@ -116,24 +117,28 @@ private void processEndpoint(final Endpoint endpoint, final RelationshipAspect r } 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, e.getBusinessPartnerNumber(), jsonUtil.asMap(e.getPolicy()))); + Tombstone.from(itemId.getGlobalAssetId(), endpoint.getProtocolInformation().getHref(), e, 0, + ProcessStep.USAGE_POLICY_VALIDATION, e.getBusinessPartnerNumber(), 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, - 0, ProcessStep.SUBMODEL_REQUEST)); + Tombstone.from(itemId.getGlobalAssetId(), endpoint.getProtocolInformation().getHref(), e, 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, - 0, ProcessStep.SUBMODEL_REQUEST)); + Tombstone.from(itemId.getGlobalAssetId(), endpoint.getProtocolInformation().getHref(), e, 0, + ProcessStep.SUBMODEL_REQUEST)); } } private static List getBpnsFrom(final List relationships) { - return relationships.stream().map(Relationship::getBpn).filter(StringUtils::isNotBlank).map(Bpn::withManufacturerId).toList(); + return relationships.stream() + .map(Relationship::getBpn) + .filter(StringUtils::isNotBlank) + .map(Bpn::withManufacturerId) + .toList(); } private List getIdsToProcess(final List relationships, diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/JobConfiguration.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/JobConfiguration.java index 94d61c09eb..8857e26c5c 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/JobConfiguration.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/JobConfiguration.java @@ -59,7 +59,6 @@ import org.eclipse.tractusx.irs.edc.client.EdcSubmodelClientImpl; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelClientLocalStub; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelFacade; -import org.eclipse.tractusx.irs.edc.client.EndpointDataReferenceStorage; import org.eclipse.tractusx.irs.edc.client.cache.endpointdatareference.EndpointDataReferenceCacheService; import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryService; import org.eclipse.tractusx.irs.registryclient.central.DigitalTwinRegistryClient; @@ -184,11 +183,10 @@ public EdcSubmodelClient edcLocalSubmodelClient(final CxTestDataContainer cxTest @Bean public EdcSubmodelClient edcSubmodelClient(final EdcConfiguration edcConfiguration, final ContractNegotiationService contractNegotiationService, final EdcDataPlaneClient edcDataPlaneClient, - final EndpointDataReferenceStorage endpointDataReferenceStorage, final AsyncPollingService pollingService, - final RetryRegistry retryRegistry, final EDCCatalogFacade catalogFacade, + final AsyncPollingService pollingService, final RetryRegistry retryRegistry, + final EDCCatalogFacade catalogFacade, final EndpointDataReferenceCacheService endpointDataReferenceCacheService) { return new EdcSubmodelClientImpl(edcConfiguration, contractNegotiationService, edcDataPlaneClient, - endpointDataReferenceStorage, pollingService, retryRegistry, catalogFacade, - endpointDataReferenceCacheService); + pollingService, retryRegistry, catalogFacade, endpointDataReferenceCacheService); } } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RegistryConfiguration.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RegistryConfiguration.java index db7de31bb7..11d7fbbf77 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RegistryConfiguration.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RegistryConfiguration.java @@ -75,7 +75,7 @@ public DecentralDigitalTwinRegistryService decentralDigitalTwinRegistryService( return new DecentralDigitalTwinRegistryService(connectorEndpointsService, new EndpointDataForConnectorsService((edcConnectorEndpoint, assetType, assetValue) -> { try { - return facade.getEndpointReferenceForAsset(edcConnectorEndpoint, assetType, assetValue); + return facade.getEndpointReferencesForAsset(edcConnectorEndpoint, assetType, assetValue); } catch (EdcClientException e) { throw new EdcRetrieverException(e); } 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 index 18209e9d6b..2d9a98bdf6 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java @@ -290,7 +290,6 @@ private void successfulRegistryAndDataRequest(final String globalAssetId, final 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(); @@ -300,6 +299,7 @@ private void successfulRegistryAndDataRequest(final String globalAssetId, final containing(globalAssetId))); stubFor(getShellDescriptor200(PUBLIC_SHELL_DESCRIPTORS_PATH + WiremockSupport.encodedId(shellId), bpn, submodelDescriptors, globalAssetId, shellId, idShort)); + successfulNegotiation(edcAssetId); } private void successfulNegotiation(final String edcAssetId) { 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 index 90c783f276..06e51406ad 100644 --- 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 @@ -155,6 +155,7 @@ private static void sleep(final int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); throw new RuntimeException(e); } } 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 ccc37dd532..0d8fae946e 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 @@ -23,6 +23,7 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.edc.client; +import java.util.List; import java.util.concurrent.CompletableFuture; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; @@ -45,10 +46,11 @@ CompletableFuture getSubmodelPayload(String connectorEndpoin CompletableFuture sendNotification(String submodelEndpointAddress, String assetId, EdcNotification notification) throws EdcClientException; - CompletableFuture getEndpointReferenceForAsset(String endpointAddress, String filterKey, - String filterValue) throws EdcClientException; + List> getEndpointReferencesForAsset(String endpointAddress, + String filterKey, String filterValue) throws EdcClientException; - CompletableFuture getEndpointReferenceForAsset(String endpointAddress, String filterKey, - String filterValue, EndpointDataReferenceStatus cachedEndpointDataReference) throws EdcClientException; + List> getEndpointReferencesForAsset(String endpointAddress, + String filterKey, String filterValue, EndpointDataReferenceStatus cachedEndpointDataReference) + throws EdcClientException; } 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 3cee65c88a..c5560a4d15 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 @@ -30,7 +30,9 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; +import java.util.stream.Stream; import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.RetryRegistry; @@ -41,7 +43,10 @@ import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.irs.edc.client.cache.endpointdatareference.EndpointDataReferenceCacheService; import org.eclipse.tractusx.irs.edc.client.cache.endpointdatareference.EndpointDataReferenceStatus; +import org.eclipse.tractusx.irs.edc.client.exceptions.ContractNegotiationException; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.edc.client.exceptions.TransferProcessException; +import org.eclipse.tractusx.irs.edc.client.exceptions.UsagePolicyException; 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; @@ -58,13 +63,14 @@ */ @Slf4j @RequiredArgsConstructor -@SuppressWarnings("PMD.TooManyMethods") +@SuppressWarnings({ "PMD.TooManyMethods", + "PMD.ExcessiveImports" +}) public class EdcSubmodelClientImpl implements EdcSubmodelClient { private final EdcConfiguration config; private final ContractNegotiationService contractNegotiationService; private final EdcDataPlaneClient edcDataPlaneClient; - private final EndpointDataReferenceStorage endpointDataReferenceStorage; private final AsyncPollingService pollingService; private final RetryRegistry retryRegistry; private final EDCCatalogFacade catalogFacade; @@ -89,14 +95,15 @@ private CompletableFuture sendNotificationAsync(final S .schedule(); } - private Optional retrieveSubmodelData(final String submodelDataplaneUrl, final StopWatch stopWatch, - final EndpointDataReference endpointDataReference) { + 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 payload = edcDataPlaneClient.getData(endpointDataReference, submodelDataplaneUrl); stopWatchOnEdcTask(stopWatch); - return Optional.of(new SubmodelDescriptor(getContractAgreementId(endpointDataReference.getAuthCode()), payload)); + return Optional.of( + new SubmodelDescriptor(getContractAgreementId(endpointDataReference.getAuthCode()), payload)); } return Optional.empty(); @@ -109,16 +116,18 @@ private String getContractAgreementId(final String authCode) { private Optional retrieveEndpointReference(final String storageId, final StopWatch stopWatch) { - final Optional dataReference = retrieveEndpointDataReferenceByContractAgreementId( - storageId); + + log.info("Retrieving dataReference from storage for storageId (assetId or contractAgreementId): {}", + Masker.mask(storageId)); + final var dataReference = endpointDataReferenceCacheService.getEndpointDataReferenceFromStorage(storageId); if (dataReference.isPresent()) { final EndpointDataReference ref = dataReference.get(); log.info("Retrieving Endpoint Reference data from EDC data plane with id: {}", ref.getId()); stopWatchOnEdcTask(stopWatch); - return Optional.of(ref); } + return Optional.empty(); } @@ -180,13 +189,13 @@ private EndpointDataReference getEndpointDataReferenceAndAddToStorage(final Stri try { final EndpointDataReference endpointDataReference = getEndpointReferenceForAsset(connectorEndpoint, NAMESPACE_EDC_ID, assetId, cachedEndpointDataReference).get(); - endpointDataReferenceStorage.put(assetId, endpointDataReference); + endpointDataReferenceCacheService.putEndpointDataReferenceIntoStorage(assetId, endpointDataReference); return endpointDataReference; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new EdcClientException(e); - } catch (ExecutionException e) { + } catch (CompletionException | ExecutionException e) { throw new EdcClientException(e); } } @@ -204,24 +213,83 @@ public CompletableFuture sendNotification(final String } @Override - public CompletableFuture getEndpointReferenceForAsset(final String endpointAddress, + public List> getEndpointReferencesForAsset(final String endpointAddress, final String filterKey, final String filterValue) throws EdcClientException { - return execute(endpointAddress, () -> getEndpointReferenceForAsset(endpointAddress, filterKey, filterValue, + return execute(endpointAddress, () -> getEndpointReferencesForAsset(endpointAddress, filterKey, filterValue, new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW))); } @Override + public List> getEndpointReferencesForAsset(final String endpointAddress, + final String filterKey, final String filterValue, + final EndpointDataReferenceStatus endpointDataReferenceStatus) throws EdcClientException { + + final StopWatch stopWatch = new StopWatch(); + stopWatch.start("Get EDC Submodel task for shell descriptor, endpoint " + endpointAddress); + + final String providerWithSuffix = appendSuffix(endpointAddress, config.getControlplane().getProviderSuffix()); + + // CatalogItem = contract offer + final List contractOffers = catalogFacade.fetchCatalogByFilter(providerWithSuffix, filterKey, + filterValue); + + if (contractOffers.isEmpty()) { + throw new EdcClientException( + "Catalog is empty for endpointAddress '%s' filterKey '%s', filterValue '%s'".formatted( + endpointAddress, filterKey, filterValue)); + } + + // We need to process each contract offer in parallel + // (see src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-multiple-DTRs.puml + // and src/docs/arc42/cross-cutting/discovery-DTR--multiple-EDCs-with-multiple-DTRs--detailed.puml) + return contractOffers.stream().map(contractOffer -> { + + final NegotiationResponse negotiationResponse; + try { + negotiationResponse = negotiateContract(endpointDataReferenceStatus, contractOffer, providerWithSuffix); + + final String storageId = getStorageId(endpointDataReferenceStatus, negotiationResponse); + + return pollingService.createJob() + .action(() -> retrieveEndpointReference(storageId, stopWatch)) + .timeToLive(config.getSubmodel().getRequestTtl()) + .description("waiting for Endpoint Reference retrieval") + .build() + .schedule(); + } catch (EdcClientException e) { + log.warn(("Negotiate contract failed for " + + "endpointDataReferenceStatus = '%s', catalogItem = '%s', providerWithSuffix = '%s' ").formatted( + endpointDataReferenceStatus, contractOffer, providerWithSuffix)); + return (CompletableFuture) Stream.empty(); + } + + }).toList(); + } + + private NegotiationResponse negotiateContract(final EndpointDataReferenceStatus endpointDataReferenceStatus, + final CatalogItem catalogItem, final String providerWithSuffix) throws EdcClientException { + final NegotiationResponse response; + try { + response = contractNegotiationService.negotiate(providerWithSuffix, catalogItem, + endpointDataReferenceStatus); + } catch (TransferProcessException | UsagePolicyException | ContractNegotiationException e) { + throw new EdcClientException(("Negotiation failed for endpoint '%s', " + "tokenStatus '%s', " + + "providerWithSuffix '%s', catalogItem '%s'").formatted( + endpointDataReferenceStatus.endpointDataReference(), endpointDataReferenceStatus.tokenStatus(), + providerWithSuffix, endpointDataReferenceStatus), e); + } + return response; + } + public CompletableFuture getEndpointReferenceForAsset(final String endpointAddress, final String filterKey, final String filterValue, final EndpointDataReferenceStatus endpointDataReferenceStatus) throws EdcClientException { final StopWatch stopWatch = new StopWatch(); stopWatch.start("Get EDC Submodel task for shell descriptor, endpoint " + endpointAddress); - final String providerWithSuffix = appendSuffix(endpointAddress, - config.getControlplane().getProviderSuffix()); + final String providerWithSuffix = appendSuffix(endpointAddress, config.getControlplane().getProviderSuffix()); - final List items = catalogFacade.fetchCatalogByFilter(providerWithSuffix, filterKey, - filterValue); + final List items = catalogFacade.fetchCatalogByFilter(providerWithSuffix, filterKey, filterValue); final NegotiationResponse response = contractNegotiationService.negotiate(providerWithSuffix, items.stream().findFirst().orElseThrow(), endpointDataReferenceStatus); @@ -263,12 +331,6 @@ private String appendSuffix(final String endpointAddress, final String providerS return addressWithSuffix; } - private Optional retrieveEndpointDataReferenceByContractAgreementId(final String storageId) { - log.info("Retrieving dataReference from storage for storageId (assetId or contractAgreementId): {}", - Masker.mask(storageId)); - return endpointDataReferenceStorage.get(storageId); - } - @SuppressWarnings({ "PMD.AvoidRethrowingException", "PMD.AvoidCatchingGenericException" }) 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 7e0e37275a..5fb8796813 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 @@ -23,6 +23,7 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.edc.client; +import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -68,14 +69,14 @@ public CompletableFuture sendNotification(final String } @Override - public CompletableFuture getEndpointReferenceForAsset(final String endpointAddress, + public List> getEndpointReferencesForAsset(final String endpointAddress, final String filterKey, final String filterValue, final EndpointDataReferenceStatus cachedEndpointDataReference) throws EdcClientException { throw new EdcClientException("Not implemented"); } @Override - public CompletableFuture getEndpointReferenceForAsset(final String endpointAddress, + public List> getEndpointReferencesForAsset(final String endpointAddress, final String filterKey, final String filterValue) throws EdcClientException { throw new EdcClientException("Not implemented"); } 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 0b5e0558f5..16040d640e 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 @@ -23,6 +23,8 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.edc.client; +import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import lombok.RequiredArgsConstructor; @@ -80,22 +82,10 @@ public EdcNotificationResponse sendNotification(final String submodelEndpointAdd throw new EdcClientException(cause); } } - - @SuppressWarnings("PMD.PreserveStackTrace") - public EndpointDataReference getEndpointReferenceForAsset(final String endpointAddress, final String filterKey, - final String filterValue) throws EdcClientException { - try { - return client.getEndpointReferenceForAsset(endpointAddress, filterKey, filterValue).get(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return null; - } catch (ExecutionException e) { - final Throwable cause = e.getCause(); - if (cause instanceof EdcClientException exceptionCause) { - throw exceptionCause; - } - throw new EdcClientException(cause); - } + + public List> getEndpointReferencesForAsset(final String endpointAddress, + final String filterKey, final String filterValue) throws EdcClientException { + return client.getEndpointReferencesForAsset(endpointAddress, filterKey, filterValue); } } diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/cache/endpointdatareference/EndpointDataReferenceCacheService.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/cache/endpointdatareference/EndpointDataReferenceCacheService.java index 264f080339..4fefed5726 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/cache/endpointdatareference/EndpointDataReferenceCacheService.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/cache/endpointdatareference/EndpointDataReferenceCacheService.java @@ -56,8 +56,9 @@ public class EndpointDataReferenceCacheService { * describing token status */ public EndpointDataReferenceStatus getEndpointDataReference(final String assetId) { - final Optional endpointDataReferenceOptional = retrieveEndpointReferenceByAssetId( - assetId); + + log.info("Retrieving dataReference from storage for assetId {}", assetId); + final Optional endpointDataReferenceOptional = endpointDataReferenceStorage.get(assetId); if (endpointDataReferenceOptional.isPresent()) { final String authCode = endpointDataReferenceOptional.get().getAuthCode(); @@ -81,9 +82,13 @@ public EndpointDataReferenceStatus getEndpointDataReference(final String assetId return new EndpointDataReferenceStatus(null, EndpointDataReferenceStatus.TokenStatus.REQUIRED_NEW); } - private Optional retrieveEndpointReferenceByAssetId(final String assetId) { - log.info("Retrieving dataReference from storage for assetId {}", assetId); - return endpointDataReferenceStorage.get(assetId); + public Optional getEndpointDataReferenceFromStorage(final String storageId) { + return endpointDataReferenceStorage.get(storageId); + } + + public void putEndpointDataReferenceIntoStorage(final String assetId, + final EndpointDataReference endpointDataReference) { + endpointDataReferenceStorage.put(assetId, endpointDataReference); } private static boolean isTokenExpired(final @NotNull String authCode) { @@ -94,6 +99,7 @@ private static boolean isTokenExpired(final @NotNull String authCode) { private static Instant extractTokenExpiration(final String token) { return Instant.ofEpochSecond(EDRAuthCode.fromAuthCodeToken(token).getExp()); } + } diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/exceptions/EdcClientException.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/exceptions/EdcClientException.java index 98ee3696b3..8066f5e431 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/exceptions/EdcClientException.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/exceptions/EdcClientException.java @@ -28,6 +28,10 @@ */ public class EdcClientException extends Exception { + public EdcClientException(final String msg, final Throwable cause) { + super(msg, cause); + } + public EdcClientException(final Throwable cause) { super(cause); } 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 5e2b06ea4b..d1c282fa63 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 @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -92,8 +93,6 @@ class EdcSubmodelClientTest extends LocalTestDataConfigurationAware { private final static String CONNECTOR_ENDPOINT = "https://connector.endpoint.com"; private final static String SUBMODEL_SUFIX = "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel"; - private final EndpointDataReferenceStorage endpointDataReferenceStorage = new EndpointDataReferenceStorage( - Duration.ofMinutes(1)); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final TimeMachine clock = new TimeMachine(); private final AsyncPollingService pollingService = new AsyncPollingService(clock, scheduler); @@ -125,21 +124,23 @@ void setUp() { config.setSubmodel(new EdcConfiguration.SubmodelConfig()); config.getSubmodel().setUrnPrefix("/urn"); config.getSubmodel().setRequestTtl(Duration.ofMinutes(10)); - testee = new EdcSubmodelClientImpl(config, contractNegotiationService, edcDataPlaneClient, - endpointDataReferenceStorage, pollingService, retryRegistry, catalogFacade, - endpointDataReferenceCacheService); + + testee = new EdcSubmodelClientImpl(config, contractNegotiationService, edcDataPlaneClient, pollingService, + retryRegistry, catalogFacade, endpointDataReferenceCacheService); } @Test void shouldRetrieveValidRelationship() throws Exception { // arrange + final String agreementId = "agreementId"; when(catalogFacade.fetchCatalogByFilter(any(), any(), any())).thenReturn( List.of(CatalogItem.builder().itemId("itemId").build())); when(contractNegotiationService.negotiate(any(), any(), eq(new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)))).thenReturn( - NegotiationResponse.builder().contractAgreementId("agreementId").build()); - final EndpointDataReference ref = TestMother.endpointDataReference("agreementId"); - endpointDataReferenceStorage.put("agreementId", ref); + NegotiationResponse.builder().contractAgreementId(agreementId).build()); + final EndpointDataReference ref = TestMother.endpointDataReference(agreementId); + when(endpointDataReferenceCacheService.getEndpointDataReferenceFromStorage(agreementId)).thenReturn( + Optional.ofNullable(ref)); final String singleLevelBomAsBuiltJson = readSingleLevelBomAsBuiltData(); when(edcDataPlaneClient.getData(eq(ref), any())).thenReturn(singleLevelBomAsBuiltJson); when(endpointDataReferenceCacheService.getEndpointDataReference("assetId")).thenReturn( @@ -156,13 +157,15 @@ void shouldRetrieveValidRelationship() throws Exception { @Test void shouldSendNotificationSuccessfully() throws Exception { // arrange + final String agreementId = "agreementId"; final EdcNotification notification = EdcNotification.builder().build(); when(catalogFacade.fetchCatalogByFilter(any(), any(), any())).thenReturn( List.of(CatalogItem.builder().itemId("itemId").build())); when(contractNegotiationService.negotiate(any(), any(), any())).thenReturn( - NegotiationResponse.builder().contractAgreementId("agreementId").build()); + NegotiationResponse.builder().contractAgreementId(agreementId).build()); final EndpointDataReference ref = mock(EndpointDataReference.class); - endpointDataReferenceStorage.put("agreementId", ref); + when(endpointDataReferenceCacheService.getEndpointDataReferenceFromStorage(agreementId)).thenReturn( + Optional.ofNullable(ref)); when(edcDataPlaneClient.sendData(ref, notification)).thenReturn(() -> true); when(endpointDataReferenceCacheService.getEndpointDataReference(any())).thenReturn( new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); @@ -196,7 +199,8 @@ void shouldReturnRelationshipsWhenRequestingWithCatenaXIdAndSingleLevelBomAsBuil new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); final String submodelResponse = testee.getSubmodelPayload("http://localhost/", "/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS).getPayload(); + .get(5, TimeUnit.SECONDS) + .getPayload(); assertThat(submodelResponse).contains(existingCatenaXId); } @@ -211,7 +215,8 @@ void shouldReturnRelationshipsWhenRequestingWithCatenaXIdAndSingleLevelBomAsPlan new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); final String submodelResponse = testee.getSubmodelPayload("http://localhost/", "/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS).getPayload(); + .get(5, TimeUnit.SECONDS) + .getPayload(); assertThat(submodelResponse).contains("urn:uuid:e5c96ab5-896a-482c-8761-efd74777ca97"); } @@ -226,7 +231,8 @@ void shouldReturnRelationshipsWhenRequestingWithCatenaXIdAndSingleLevelBomAsSpec new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); final String submodelResponse = testee.getSubmodelPayload("http://localhost/", "/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS).getPayload(); + .get(5, TimeUnit.SECONDS) + .getPayload(); assertThat(submodelResponse).contains("urn:uuid:2afbac90-a662-4f16-9058-4f030e692631"); } @@ -241,7 +247,8 @@ void shouldReturnEmptyRelationshipsWhenRequestingWithCatenaXIdAndSingleLevelUsag new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); final String submodelResponse = testee.getSubmodelPayload("http://localhost/", "/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS).getPayload(); + .get(5, TimeUnit.SECONDS) + .getPayload(); assertThat(submodelResponse).isNotEmpty(); } @@ -257,7 +264,8 @@ void shouldReturnEmptyRelationshipsWhenRequestingWithNotExistingCatenaXIdAndSing new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); final String submodelResponse = testee.getSubmodelPayload("http://localhost/", "/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS).getPayload(); + .get(5, TimeUnit.SECONDS) + .getPayload(); assertThat(submodelResponse).isEqualTo("{}"); } @@ -272,8 +280,9 @@ void shouldReturnRawSerialPartWhenExisting() throws Exception { new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); final String submodelResponse = testee.getSubmodelPayload("https://connector.endpoint.com", - "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS).getPayload(); + "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", ASSET_ID) + .get(5, TimeUnit.SECONDS) + .getPayload(); assertThat(submodelResponse).startsWith( "{\"localIdentifiers\":[{\"value\":\"BPNL00000003AVTH\",\"key\":\"manufacturerId\"}"); @@ -290,8 +299,9 @@ void shouldUseDecodedTargetId() throws Exception { new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); final String submodelResponse = testee.getSubmodelPayload("https://connector.endpoint.com", - "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", ASSET_ID) - .get(5, TimeUnit.SECONDS).getPayload(); + "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", ASSET_ID) + .get(5, TimeUnit.SECONDS) + .getPayload(); assertThat(submodelResponse).startsWith( "{\"localIdentifiers\":[{\"value\":\"BPNL00000003AVTH\",\"key\":\"manufacturerId\"}"); @@ -343,12 +353,13 @@ void shouldRetrieveEndpointReferenceForAsset() throws Exception { eq(new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)))).thenReturn( NegotiationResponse.builder().contractAgreementId(agreementId).build()); final EndpointDataReference expected = mock(EndpointDataReference.class); - endpointDataReferenceStorage.put(agreementId, expected); + when(endpointDataReferenceCacheService.getEndpointDataReferenceFromStorage(agreementId)).thenReturn( + Optional.ofNullable(expected)); // act - final var result = testee.getEndpointReferenceForAsset(ENDPOINT_ADDRESS, filterKey, filterValue, + final var result = testee.getEndpointReferencesForAsset(ENDPOINT_ADDRESS, filterKey, filterValue, new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); - final EndpointDataReference actual = result.get(5, TimeUnit.SECONDS); + final EndpointDataReference actual = result.get(0).get(5, TimeUnit.SECONDS); // assert assertThat(actual).isEqualTo(expected); @@ -366,11 +377,12 @@ void shouldRetrieveEndpointReferenceForAsset2() throws Exception { eq(new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)))).thenReturn( NegotiationResponse.builder().contractAgreementId(agreementId).build()); final EndpointDataReference expected = mock(EndpointDataReference.class); - endpointDataReferenceStorage.put(agreementId, expected); + when(endpointDataReferenceCacheService.getEndpointDataReferenceFromStorage(agreementId)).thenReturn( + Optional.ofNullable(expected)); // act - final var result = testee.getEndpointReferenceForAsset(ENDPOINT_ADDRESS, filterKey, filterValue); - final EndpointDataReference actual = result.get(5, TimeUnit.SECONDS); + final var result = testee.getEndpointReferencesForAsset(ENDPOINT_ADDRESS, filterKey, filterValue); + final EndpointDataReference actual = result.get(0).get(5, TimeUnit.SECONDS); // assert assertThat(actual).isEqualTo(expected); @@ -381,8 +393,7 @@ void shouldUseCachedEndpointReferenceValueWhenTokenIsValid() throws EdcClientException, ExecutionException, InterruptedException { // given when(endpointDataReferenceCacheService.getEndpointDataReference(any())).thenReturn( - new EndpointDataReferenceStatus(TestMother.endpointDataReference("assetId"), - TokenStatus.VALID)); + new EndpointDataReferenceStatus(TestMother.endpointDataReference("assetId"), TokenStatus.VALID)); final String value = "result"; when(edcDataPlaneClient.getData(any(), any())).thenReturn(value); @@ -397,33 +408,38 @@ void shouldUseCachedEndpointReferenceValueWhenTokenIsValid() @Test void shouldCreateCacheRecordWhenTokenIsNotValid() throws EdcClientException { - // given + // arrange + final String agreementId = "agreementId"; when(catalogFacade.fetchCatalogByFilter(any(), any(), any())).thenReturn( List.of(CatalogItem.builder().itemId("itemId").build())); when(contractNegotiationService.negotiate(any(), any(), eq(new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)))).thenReturn( - NegotiationResponse.builder().contractAgreementId("agreementId").build()); + NegotiationResponse.builder().contractAgreementId(agreementId).build()); final EndpointDataReference ref = mock(EndpointDataReference.class); - endpointDataReferenceStorage.put("agreementId", ref); + when(endpointDataReferenceCacheService.getEndpointDataReferenceFromStorage(agreementId)).thenReturn( + Optional.ofNullable(ref)); when(endpointDataReferenceCacheService.getEndpointDataReference(any())).thenReturn( new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)); - // when + // act testee.getSubmodelPayload(ENDPOINT_ADDRESS, "suffix", "assetId"); - // then - final Optional referenceFromStorage = endpointDataReferenceStorage.get("assetId"); - assertThat(referenceFromStorage).isPresent(); + // assert + verify(endpointDataReferenceCacheService, times(1)).putEndpointDataReferenceIntoStorage("assetId", ref); } private void prepareTestdata(final String catenaXId, final String submodelDataSuffix) throws ContractNegotiationException, IOException, UsagePolicyException, TransferProcessException { + final String agreementId = "agreementId"; when(contractNegotiationService.negotiate(any(), any(), eq(new EndpointDataReferenceStatus(null, TokenStatus.REQUIRED_NEW)))).thenReturn( - NegotiationResponse.builder().contractAgreementId("agreementId").build()); - final EndpointDataReference ref = TestMother.endpointDataReference("agreementId"); - endpointDataReferenceStorage.put("agreementId", ref); + NegotiationResponse.builder().contractAgreementId(agreementId).build()); + + final EndpointDataReference ref = TestMother.endpointDataReference(agreementId); + when(endpointDataReferenceCacheService.getEndpointDataReferenceFromStorage(agreementId)).thenReturn( + Optional.ofNullable(ref)); + final SubmodelTestdataCreator submodelTestdataCreator = new SubmodelTestdataCreator( localTestDataConfiguration.cxTestDataContainer()); final String data = StringMapper.mapToString( 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 5b384f1ccc..e5bc52abb3 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 @@ -29,6 +29,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -158,50 +159,38 @@ void shouldThrowEdcClientExceptionForNotification() throws EdcClientException { } @Nested - @DisplayName("getEndpointReferenceForAsset") - class GetEndpointReferenceForAssetTests { + @DisplayName("getEndpointReferencesForAsset") + class GetEndpointReferencesForAssetTests { @Test void shouldThrowEdcClientExceptionForEndpointReference() throws EdcClientException { // arrange final EdcClientException e = new EdcClientException("test"); - when(client.getEndpointReferenceForAsset(any(), any(), any())).thenThrow(e); + when(client.getEndpointReferencesForAsset(any(), any(), any())).thenThrow(e); // act - ThrowableAssert.ThrowingCallable action = () -> testee.getEndpointReferenceForAsset("", "", ""); + ThrowableAssert.ThrowingCallable action = () -> testee.getEndpointReferencesForAsset("", "", ""); // 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("", "", ""); + void shouldReturnFailedFuture() throws EdcClientException { - // 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); + when(client.getEndpointReferencesForAsset(any(), any(), any())).thenReturn( + List.of(CompletableFuture.failedFuture(new EdcClientException("test")))); // act - testee.getEndpointReferenceForAsset("", "", ""); + final List> results = testee.getEndpointReferencesForAsset("", "", + ""); // assert - assertThat(Thread.currentThread().isInterrupted()).isTrue(); + assertThat(results).hasSize(1); + assertThatThrownBy(() -> results.get(0).get()).isInstanceOf(ExecutionException.class) + .extracting(Throwable::getCause) + .isInstanceOf(EdcClientException.class); } } 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 f96987c366..23510866e6 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 @@ -81,20 +81,24 @@ @WireMockTest class SubmodelFacadeWiremockTest { + 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 EndpointDataReferenceStorage storage; + private EdcSubmodelClient edcSubmodelClient; private AcceptedPoliciesProvider acceptedPoliciesProvider; @BeforeEach void configureSystemUnderTest(WireMockRuntimeInfo wireMockRuntimeInfo) { + final RestTemplate restTemplate = restTemplateProxy(PROXY_SERVER_HOST, wireMockRuntimeInfo.getHttpPort()); + final EdcConfiguration config = new EdcConfiguration(); config.getControlplane().getEndpoint().setData("http://controlplane.test"); config.getControlplane().getEndpoint().setCatalog("/catalog/request"); config.getControlplane().getEndpoint().setContractNegotiation("/contractnegotiations"); @@ -121,22 +125,24 @@ void configureSystemUnderTest(WireMockRuntimeInfo wireMockRuntimeInfo) { final EdcDataPlaneClient dataPlaneClient = new EdcDataPlaneClient(restTemplate); final EDCCatalogFacade catalogFacade = new EDCCatalogFacade(controlPlaneClient, config); + + storage = new EndpointDataReferenceStorage(Duration.ofMinutes(1)); final EndpointDataReferenceCacheService endpointDataReferenceCacheService = new EndpointDataReferenceCacheService( - new EndpointDataReferenceStorage(Duration.ofMinutes(1))); + storage); acceptedPoliciesProvider = mock(AcceptedPoliciesProvider.class); when(acceptedPoliciesProvider.getAcceptedPolicies()).thenReturn(List.of(new AcceptedPolicy(policy("IRS Policy", 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)))); + 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, policyCheckerService, config); final RetryRegistry retryRegistry = RetryRegistry.ofDefaults(); - this.edcSubmodelClient = new EdcSubmodelClientImpl(config, contractNegotiationService, dataPlaneClient, storage, + this.edcSubmodelClient = new EdcSubmodelClientImpl(config, contractNegotiationService, dataPlaneClient, pollingService, retryRegistry, catalogFacade, endpointDataReferenceCacheService); } @@ -193,8 +199,7 @@ void shouldThrowExceptionWhenPoliciesAreNotAccepted() { final List andConstraints = List.of( new Constraint("Membership", new Operator(OperatorType.EQ), "active")); final ArrayList orConstraints = new ArrayList<>(); - final Permission permission = new Permission(PolicyType.USE, - new Constraints(andConstraints, orConstraints)); + final Permission permission = new Permission(PolicyType.USE, new Constraints(andConstraints, orConstraints)); final AcceptedPolicy acceptedPolicy = new AcceptedPolicy(policy("IRS Policy", List.of(permission)), OffsetDateTime.now().plusYears(1)); 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 7758811e42..2e77ca8da2 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 @@ -82,7 +82,6 @@ void setUp() { Duration.ofMinutes(1)); final EdcSubmodelClient client = new EdcSubmodelClientImpl(config, negotiationService, dataPlaneClient, - endpointDataReferenceStorage, pollingService, retryRegistry, catalogFacade, endpointDataReferenceCacheService); testee = new EdcSubmodelFacade(client); } @@ -94,14 +93,14 @@ void shouldRetryExecutionOfGetSubmodelOnClientMaxAttemptTimes() { eq(String.class))).willThrow( new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR, "EDC remote exception")); - when(endpointDataReferenceCacheService.getEndpointDataReference("9300395e-c0a5-4e88-bc57-a3973fec4c26")).thenReturn(new EndpointDataReferenceStatus(null, EndpointDataReferenceStatus.TokenStatus.REQUIRED_NEW)); + when(endpointDataReferenceCacheService.getEndpointDataReference( + "9300395e-c0a5-4e88-bc57-a3973fec4c26")).thenReturn( + new EndpointDataReferenceStatus(null, EndpointDataReferenceStatus.TokenStatus.REQUIRED_NEW)); // Act - assertThatThrownBy(() -> testee.getSubmodelPayload( - "https://connector.endpoint.com", + assertThatThrownBy(() -> testee.getSubmodelPayload("https://connector.endpoint.com", "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", - "9300395e-c0a5-4e88-bc57-a3973fec4c26")).hasCauseInstanceOf( - HttpServerErrorException.class); + "9300395e-c0a5-4e88-bc57-a3973fec4c26")).hasCauseInstanceOf(HttpServerErrorException.class); // Assert verify(restTemplate, times(retryRegistry.getDefaultConfig().getMaxAttempts())).exchange(any(String.class), @@ -113,11 +112,12 @@ void shouldRetryOnAnyRuntimeException() { // Arrange given(restTemplate.exchange(any(String.class), eq(HttpMethod.POST), any(HttpEntity.class), eq(String.class))).willThrow(new RuntimeException("EDC remote exception")); - when(endpointDataReferenceCacheService.getEndpointDataReference("9300395e-c0a5-4e88-bc57-a3973fec4c26")).thenReturn(new EndpointDataReferenceStatus(null, EndpointDataReferenceStatus.TokenStatus.REQUIRED_NEW)); + when(endpointDataReferenceCacheService.getEndpointDataReference( + "9300395e-c0a5-4e88-bc57-a3973fec4c26")).thenReturn( + new EndpointDataReferenceStatus(null, EndpointDataReferenceStatus.TokenStatus.REQUIRED_NEW)); // Act - assertThatThrownBy(() -> testee.getSubmodelPayload( - "https://connector.endpoint.com", + assertThatThrownBy(() -> testee.getSubmodelPayload("https://connector.endpoint.com", "/shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel", "9300395e-c0a5-4e88-bc57-a3973fec4c26")).hasCauseInstanceOf(RuntimeException.class); diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DefaultConfiguration.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DefaultConfiguration.java index 613c12db3c..307a80fac5 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DefaultConfiguration.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DefaultConfiguration.java @@ -36,7 +36,6 @@ import org.eclipse.tractusx.irs.edc.client.EdcSubmodelClient; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelClientImpl; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelFacade; -import org.eclipse.tractusx.irs.edc.client.EndpointDataReferenceStorage; import org.eclipse.tractusx.irs.edc.client.cache.endpointdatareference.EndpointDataReferenceCacheService; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; import org.eclipse.tractusx.irs.registryclient.central.CentralDigitalTwinRegistryService; @@ -118,7 +117,7 @@ public ConnectorEndpointsService connectorEndpointsService(final DiscoveryFinder public EndpointDataForConnectorsService endpointDataForConnectorsService(final EdcSubmodelFacade facade) { return new EndpointDataForConnectorsService((edcConnectorEndpoint, assetType, assetValue) -> { try { - return facade.getEndpointReferenceForAsset(edcConnectorEndpoint, assetType, assetValue); + return facade.getEndpointReferencesForAsset(edcConnectorEndpoint, assetType, assetValue); } catch (EdcClientException e) { throw new EdcRetrieverException(e); } @@ -134,12 +133,12 @@ public EdcSubmodelFacade edcSubmodelFacade(final EdcSubmodelClient client) { @ConditionalOnProperty(prefix = CONFIG_PREFIX, name = CONFIG_FIELD_TYPE, havingValue = CONFIG_VALUE_DECENTRAL) public EdcSubmodelClient edcSubmodelClient(final EdcConfiguration edcConfiguration, final ContractNegotiationService contractNegotiationService, final EdcDataPlaneClient edcDataPlaneClient, - final EndpointDataReferenceStorage endpointDataReferenceStorage, final AsyncPollingService pollingService, - final RetryRegistry retryRegistry, final EDCCatalogFacade catalogFacade, + final AsyncPollingService pollingService, final RetryRegistry retryRegistry, + final EDCCatalogFacade catalogFacade, final EndpointDataReferenceCacheService endpointDataReferenceCacheService) { + return new EdcSubmodelClientImpl(edcConfiguration, contractNegotiationService, edcDataPlaneClient, - endpointDataReferenceStorage, pollingService, retryRegistry, catalogFacade, - endpointDataReferenceCacheService); + pollingService, retryRegistry, catalogFacade, endpointDataReferenceCacheService); } @Bean 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 9fb4065879..1dae81a70f 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 @@ -121,8 +121,8 @@ public Collection fetchShells(final Collection ke } } - private Stream fetchShellDescriptors( - final Map.Entry> entry, final Set calledEndpoints) { + private Stream fetchShellDescriptors(final Map.Entry> entry, + final Set calledEndpoints) { try { @@ -140,8 +140,8 @@ private Stream fetchShellDescriptors( } } - private CompletableFuture> fetchShellDescriptors( - final Set calledEndpoints, final String bpn, final List keys) { + 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); @@ -149,12 +149,13 @@ private CompletableFuture> fetchShellDescriptors( log.info(msg); try { - final var connectorEndpoints = connectorEndpointsService.fetchConnectorEndpoints(bpn); - log.info("Found {} connector endpoints for bpn '{}'", connectorEndpoints.size(), bpn); - calledEndpoints.addAll(connectorEndpoints); + final var edcUrls = connectorEndpointsService.fetchConnectorEndpoints(bpn); - return fetchShellDescriptorsForConnectorEndpoints(keys, connectorEndpoints); + log.info("Found {} connector endpoints for bpn '{}'", edcUrls.size(), bpn); + calledEndpoints.addAll(edcUrls); + + return fetchShellDescriptorsForConnectorEndpoints(keys, edcUrls); } finally { watch.stop(); @@ -163,31 +164,34 @@ private CompletableFuture> fetchShellDescriptors( } private CompletableFuture> fetchShellDescriptorsForConnectorEndpoints( - final List keys, final List connectorEndpoints) { + final List keys, final List edcUrls) { final var service = endpointDataForConnectorsService; - final var futures = service.createFindEndpointDataForConnectorsFutures(connectorEndpoints) - .stream() - .map(edrFuture -> edrFuture.thenCompose(edr -> CompletableFuture.supplyAsync( - () -> fetchShellDescriptorsForKey(keys, edr)))) - .toList(); + final var shellsFuture = service.createFindEndpointDataForConnectorsFutures(edcUrls) + .stream() + .map(edrFuture -> edrFuture.thenCompose(edr -> CompletableFuture.supplyAsync( + () -> fetchShellDescriptorsForKey(keys, edr)))) + .toList(); - log.debug("Created {} futures", futures.size()); + log.debug("Created {} futures", shellsFuture.size()); - return resultFinder.getFastestResult(futures); + return resultFinder.getFastestResult(shellsFuture); } - private List fetchShellDescriptorsForKey( - final List keys, final EndpointDataReference endpointDataReference) { + 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(); + 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()); @@ -212,10 +216,7 @@ private AssetAdministrationShellDescriptor fetchShellDescriptor(final EndpointDa } private String contractNegotiationId(final String token) { - return Optional.ofNullable(token) - .map(EDRAuthCode::fromAuthCodeToken) - .map(EDRAuthCode::getCid) - .orElse(""); + return Optional.ofNullable(token).map(EDRAuthCode::fromAuthCodeToken).map(EDRAuthCode::getCid).orElse(""); } /** @@ -224,14 +225,14 @@ private String contractNegotiationId(final String token) { * If the ID is a globalAssetId, the corresponding shellId will be returned. * * @param endpointDataReference the reference to access the digital twin registry - * @param key the ambiguous key (shellId or globalAssetId) - * @return the shellId + * @param providedId the ambiguous ID (shellId or globalAssetId) + * @return the corresponding asset administration shell ID */ @NotNull - private String mapToShellId(final EndpointDataReference endpointDataReference, final String key) { + private String mapToShellId(final EndpointDataReference endpointDataReference, final String providedId) { final var watch = new StopWatch(); - final String msg = "Mapping '%s' to shell ID for endpoint '%s'".formatted(key, + final String msg = "Mapping '%s' to shell ID for endpoint '%s'".formatted(providedId, endpointDataReference.getEndpoint()); watch.start(msg); log.info(msg); @@ -240,22 +241,27 @@ private String mapToShellId(final EndpointDataReference endpointDataReference, f final var identifierKeyValuePair = IdentifierKeyValuePair.builder() .name("globalAssetId") - .value(key) + .value(providedId) .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); + + // Try to map the provided ID to the corresponding asset administration shell ID + final var mappingResultStream = decentralDigitalTwinRegistryClient.getAllAssetAdministrationShellIdsByAssetLink( + endpointDataReference, List.of(identifierKeyValuePair)).getResult().stream(); + + // Special scenario: Multiple DTs with the same globalAssetId in one DTR, see: + // docs/arc42/cross-cutting/discovery-DTR--multiple-DTs-with-the-same-globalAssedId-in-one-DTR.puml + final var mappingResult = mappingResultStream.findFirst(); + + // Empty Optional means that the ID is already a shellId + final var shellId = mappingResult.orElse(providedId); + + if (providedId.equals(shellId)) { + log.info("Found shell with shellId {} in registry", shellId); } else { - log.info("Retrieved shellId {} for globalAssetId {}", aaShellIdentification, key); + log.info("Retrieved shellId {} for globalAssetId {}", shellId, providedId); } - return aaShellIdentification; + return shellId; } finally { watch.stop(); @@ -270,12 +276,11 @@ private Collection lookupShellIds(final String bpn) throws RegistryServi try { - final var connectorEndpoints = connectorEndpointsService.fetchConnectorEndpoints(bpn); - log.info("Looking up shell ids for bpn '{}' with connector endpoints {}", bpn, connectorEndpoints); + final var edcUrls = connectorEndpointsService.fetchConnectorEndpoints(bpn); + log.info("Looking up shell ids for bpn '{}' with connector endpoints {}", bpn, edcUrls); final var endpointDataReferenceFutures = endpointDataForConnectorsService.createFindEndpointDataForConnectorsFutures( - connectorEndpoints); - log.debug("Created endpointDataReferenceFutures"); + edcUrls); return lookupShellIds(bpn, endpointDataReferenceFutures); diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcEndpointReferenceRetriever.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcEndpointReferenceRetriever.java index a78f7aba81..d27f5748c7 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcEndpointReferenceRetriever.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcEndpointReferenceRetriever.java @@ -23,6 +23,9 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.registryclient.decentral; +import java.util.List; +import java.util.concurrent.CompletableFuture; + import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; /** @@ -31,13 +34,14 @@ public interface EdcEndpointReferenceRetriever { /** - * Retrieves the EDC endpoint reference from the specified connector endpoint and asset combination + * Retrieves the EDC endpoint references from the specified connector endpoint and asset combination + * * @param edcConnectorEndpoint the endpoint URL - * @param assetType the asset type id - * @param assetValue the asset type value - * @return the endpoint data reference + * @param assetType the asset type id + * @param assetValue the asset type value + * @return the endpoint data references as list of futures * @throws EdcRetrieverException on any EDC errors */ - EndpointDataReference getEndpointReferenceForAsset(String edcConnectorEndpoint, String assetType, String assetValue) - throws EdcRetrieverException; + List> getEndpointReferencesForAsset(String edcConnectorEndpoint, + String assetType, String assetValue) throws EdcRetrieverException; } 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 f6442bdaf2..7b2af22161 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,9 @@ ********************************************************************************/ 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; @@ -50,20 +47,19 @@ public class EndpointDataForConnectorsService { private final EdcEndpointReferenceRetriever edcSubmodelFacade; public List> createFindEndpointDataForConnectorsFutures( - final List connectorEndpoints) { + final List edcUrls) { final var watch = new StopWatch(); - final String msg = "Creating futures to get EndpointDataReferences for endpoints: %s".formatted( - connectorEndpoints); + final String msg = "Creating futures to get EndpointDataReferences for endpoints: %s".formatted(edcUrls); watch.start(msg); log.info(msg); List> futures = Collections.emptyList(); try { - futures = connectorEndpoints.stream() - .map(connectorEndpoint -> supplyAsync( - () -> getEndpointReferenceForAsset(connectorEndpoint))) - .toList(); + log.info("Creating futures to get EndpointDataReferences for endpoints: {}", edcUrls); + futures = edcUrls.stream() + .flatMap(edcUrl -> createGetEndpointReferencesForAssetFutures(edcUrl).stream()) + .toList(); return futures; } finally { log.info("Created {} futures", futures.size()); @@ -72,19 +68,20 @@ public List> createFindEndpointDataForC } } - private EndpointDataReference getEndpointReferenceForAsset(final String connector) { + private List> createGetEndpointReferencesForAssetFutures( + final String edcUrl) { final var watch = new StopWatch(); - final String msg = "Trying to retrieve EndpointDataReference for connector '%s'".formatted(connector); + final String msg = "Trying to retrieve EndpointDataReference for connector '%s'".formatted(edcUrl); watch.start(msg); log.info(msg); try { - return edcSubmodelFacade.getEndpointReferenceForAsset(connector, DT_REGISTRY_ASSET_TYPE, + return edcSubmodelFacade.getEndpointReferencesForAsset(edcUrl, 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); + log.warn("Exception occurred when retrieving EndpointDataReference from connector '{}'", edcUrl, e); + return List.of(CompletableFuture.failedFuture(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 590eb66a5b..a5333b659d 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 @@ -44,6 +44,12 @@ public class ConnectorEndpointsService { private final DiscoveryFinderClient discoveryFinderClient; private static final String CONNECTOR_ENDPOINT_SERVICE_CACHE_NAME = "connector_endpoint_service_cache"; + /** + * Get EDCs for BPN. + * + * @param bpn the BPN + * @return list of EDC URLs + */ @Cacheable(CONNECTOR_ENDPOINT_SERVICE_CACHE_NAME) public List fetchConnectorEndpoints(final String bpn) { 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 4a3b67fd5f..274d8387b2 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 @@ -32,6 +32,7 @@ import static org.mockito.Mockito.when; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; @@ -82,7 +83,8 @@ void endpointDataForConnectorsService() throws EdcClientException { 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); + when(mock.getEndpointReferencesForAsset(eq(endpointAddress), any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); // ACT final var endpointDataForConnectorsService = testee.endpointDataForConnectorsService(mock); @@ -91,19 +93,22 @@ void endpointDataForConnectorsService() throws EdcClientException { .forEach(future -> { try { future.get(); - } catch (InterruptedException | ExecutionException e) { + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (ExecutionException e) { throw new RuntimeException(e); } }); // ASSERT - verify(mock).getEndpointReferenceForAsset(eq(endpointAddress), any(), any()); + verify(mock).getEndpointReferencesForAsset(eq(endpointAddress), any(), any()); } @Test void endpointDataForConnectorsService_withException() throws EdcClientException { final var mock = mock(EdcSubmodelFacade.class); - when(mock.getEndpointReferenceForAsset(any(), any(), any())).thenThrow(new EdcClientException("test")); + when(mock.getEndpointReferencesForAsset(any(), any(), any())).thenThrow(new EdcClientException("test")); final var endpointDataForConnectorsService = testee.endpointDataForConnectorsService(mock); final var dummyEndpoints = List.of("test"); 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 926b9de649..abf3eb481d 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 @@ -141,6 +141,8 @@ void whenInterruptedExceptionOccurs() throws ExecutionException, InterruptedExce .satisfies(e -> assertThat( ((ShellNotFoundException) e).getCalledEndpoints()).containsExactlyInAnyOrder( "address1", "address2")); + + assertThat(Thread.currentThread().isInterrupted()).isTrue(); } @Test 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 index 36ce0ff1b6..31418299ee 100644 --- 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 @@ -48,14 +48,18 @@ 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.ArgumentMatchers.eq; 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.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; @@ -63,6 +67,7 @@ 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.exceptions.EdcClientException; import org.eclipse.tractusx.irs.edc.client.model.EDRAuthCode; import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryKey; import org.eclipse.tractusx.irs.registryclient.discovery.ConnectorEndpointsService; @@ -70,127 +75,263 @@ 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.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import org.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 final EdcEndpointReferenceRetriever edcEndpointReferenceRetrieverMock = mock( + EdcEndpointReferenceRetriever.class); private DecentralDigitalTwinRegistryService decentralDigitalTwinRegistryService; @BeforeEach - void setUp(WireMockRuntimeInfo wireMockRuntimeInfo) throws EdcRetrieverException { + void setUp(WireMockRuntimeInfo wireMockRuntimeInfo) { 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 endpointDataForConnectorsService = new EndpointDataForConnectorsService( + edcEndpointReferenceRetrieverMock); 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 + ".*"))); - } + @Nested + class FetchShellsTests { + @Test + void shouldDiscoverEDCAndRequestRegistry() throws RegistryServiceException, EdcRetrieverException { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + givenThat(getLookupShells200()); + givenThat(getShellDescriptor200()); - @Test - void shouldThrowInCaseOfDiscoveryError() { - // Arrange - givenThat(postDiscoveryFinder404()); - final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + final var endpointDataReference = endpointDataReference("assetId"); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); - // Act & Assert - assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( - ShellNotFoundException.class); - verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); - } + // Act + final Collection shells = decentralDigitalTwinRegistryService.fetchShells( + List.of(new DigitalTwinRegistryKey("testId", TEST_BPN))); - @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))); - } + // 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 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 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() throws EdcRetrieverException { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + + final var endpointDataReference = endpointDataReference("assetId"); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); + + 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() throws EdcRetrieverException { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + + final var endpointDataReference = endpointDataReference("assetId"); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); + + 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() throws EdcRetrieverException { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); - @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 + ".*"))); + final var endpointDataReference = endpointDataReference("assetId"); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); + + 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 + ".*"))); + } } - @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 + ".*"))); + @Nested + class LookupShellIdentifiersTests { + + @Test + void lookupShellIdentifiers_oneEDC_oneDTR() throws RegistryServiceException, EdcRetrieverException { + // Arrange + givenThat(postDiscoveryFinder200()); + final List edcUrls = List.of("https://test.edc.io"); + givenThat(postEdcDiscovery200(TEST_BPN, edcUrls)); + givenThat(getLookupShells200()); + + // simulate endpoint data reference + final var endpointDataReference = endpointDataReference("assetId"); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); + + // Act + final Collection digitalTwinRegistryKeys = decentralDigitalTwinRegistryService.lookupShellIdentifiers( + TEST_BPN); + + // Assert + assertThat(digitalTwinRegistryKeys).hasSize(1); + assertThat(digitalTwinRegistryKeys.stream().findFirst().get().shellId()).isEqualTo( + "urn:uuid:21f7ebea-fa8a-410c-a656-bd9082e67dcf"); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + verify(exactly(edcUrls.size()), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + } + + @ParameterizedTest(name = "{0}") + @ArgumentsSource(NoOrFailedEndpointDataReferenceProvider.class) + void lookupShellIdentifiers_multipleEDCs_oneDTR(String title, + List> endpointDataReferenceForAssetFutures) + throws RegistryServiceException, EdcRetrieverException { + // Arrange + givenThat(postDiscoveryFinder200()); + final String edc1Url = "https://test.edc1.io"; + final String edc2Url = "https://test.edc2.io"; + final List edcUrls = List.of(edc1Url, edc2Url); + givenThat(postEdcDiscovery200(TEST_BPN, edcUrls)); + givenThat(getLookupShells200()); + + // simulate endpoint data reference + final var endpointDataReference = endpointDataReference("assetId"); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc1Url), any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc2Url), any(), any())).thenReturn( + endpointDataReferenceForAssetFutures); + + // Act + final Collection digitalTwinRegistryKeys = decentralDigitalTwinRegistryService.lookupShellIdentifiers( + TEST_BPN); + + // Assert + assertThat(digitalTwinRegistryKeys).hasSize(1); + assertThat(digitalTwinRegistryKeys.stream().findFirst().get().shellId()).isEqualTo( + "urn:uuid:21f7ebea-fa8a-410c-a656-bd9082e67dcf"); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + // because just one DTR + verify(exactly(1), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + } + + public static class NoOrFailedEndpointDataReferenceProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(final ExtensionContext extensionContext) { + return Stream.of( + // failed future + Arguments.of("given failed future", List.of(CompletableFuture.failedFuture( + new EdcRetrieverException(new EdcClientException(new RuntimeException("test")))))), + // no result + Arguments.of("given no result", Collections.emptyList())); + } + } + + @Test + void lookupShellIdentifiers_multipleEDCs_multipleDTRs() throws RegistryServiceException, EdcRetrieverException { + // Arrange + givenThat(postDiscoveryFinder200()); + final String edc1Url = "https://test.edc1.io"; + final String edc2Url = "https://test.edc2.io"; + final List edcUrls = List.of(edc1Url, edc2Url); + givenThat(postEdcDiscovery200(TEST_BPN, edcUrls)); + givenThat(getLookupShells200()); + + // simulate endpoint data reference + final var endpointDataReference1 = endpointDataReference("dtr1-assetId"); + final var endpointDataReference2 = endpointDataReference("dtr2-assetId"); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc1Url), any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference1))); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc2Url), any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference2))); + + // Act & Assert + final Collection digitalTwinRegistryKeys = decentralDigitalTwinRegistryService.lookupShellIdentifiers( + TEST_BPN); + + // Assert + assertThat(digitalTwinRegistryKeys).hasSize(1); + assertThat(digitalTwinRegistryKeys.stream().findFirst().get().shellId()).isEqualTo( + "urn:uuid:21f7ebea-fa8a-410c-a656-bd9082e67dcf"); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + // multiple DTR (one per EDC) + verify(exactly(edcUrls.size()), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + } } private EndpointDataReference endpointDataReference(final String contractAgreementId) { 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 085b015d8b..b4a8b9f922 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 @@ -62,8 +62,9 @@ class EndpointDataForConnectorsServiceTest { void shouldReturnExpectedEndpointDataReference() throws EdcRetrieverException { // GIVEN - when(edcSubmodelFacade.getEndpointReferenceForAsset(connectionOneAddress, DT_REGISTRY_ASSET_TYPE, - DT_REGISTRY_ASSET_VALUE)).thenReturn(CONNECTION_ONE_DATA_REF); + when(edcSubmodelFacade.getEndpointReferencesForAsset(connectionOneAddress, DT_REGISTRY_ASSET_TYPE, + DT_REGISTRY_ASSET_VALUE)).thenReturn( + List.of(CompletableFuture.completedFuture(CONNECTION_ONE_DATA_REF))); // WHEN final List> endpointDataReferences = sut.createFindEndpointDataForConnectorsFutures( @@ -83,13 +84,14 @@ void shouldReturnExpectedEndpointDataReferenceFromSecondConnectionEndpoint() thr // GIVEN // a first endpoint failing (1) - when(edcSubmodelFacade.getEndpointReferenceForAsset(connectionOneAddress, DT_REGISTRY_ASSET_TYPE, + when(edcSubmodelFacade.getEndpointReferencesForAsset(connectionOneAddress, DT_REGISTRY_ASSET_TYPE, DT_REGISTRY_ASSET_VALUE)).thenThrow( new EdcRetrieverException(new EdcClientException("EdcClientException"))); // 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(edcSubmodelFacade.getEndpointReferencesForAsset(connectionTwoAddress, DT_REGISTRY_ASSET_TYPE, + DT_REGISTRY_ASSET_VALUE)).thenReturn( + List.of(CompletableFuture.completedFuture(CONNECTION_TWO_DATA_REF))); // WHEN final List> dataRefFutures = // @@ -123,7 +125,7 @@ private static EndpointDataReference executeFutureMappingErrorsToNull( void shouldThrowExceptionWhenConnectorEndpointsNotReachable() throws EdcRetrieverException { // GIVEN - when(edcSubmodelFacade.getEndpointReferenceForAsset(anyString(), eq(DT_REGISTRY_ASSET_TYPE), + when(edcSubmodelFacade.getEndpointReferencesForAsset(anyString(), eq(DT_REGISTRY_ASSET_TYPE), eq(DT_REGISTRY_ASSET_VALUE))).thenThrow( new EdcRetrieverException(new EdcClientException("EdcClientException"))); 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 index cd38948422..10fe132070 100644 --- 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 @@ -56,14 +56,14 @@ 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"); + 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))));