From 554ea71ee1fdc68ac5730334310add32e5197aa4 Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Wed, 26 Apr 2023 11:06:53 +0200 Subject: [PATCH] feat(dsp): preparatory work for 0.4.0, i.e. the new DSP * feat(Deps): updates to the EDC snapshot and implements a version catalog * pr remarks * pr remarks feat(ControlPlaneAdapter): callback integration for transfer process (#293) * feat(ControlPlaneAdapter): callback integration for transfer process * pr remarks * adds E2E test for opening a transfer * pr remarks * pr remarks * pr remarks + fix due snapshot upgrade * switched to EDC 0.0.1-20230509-preview-SNAPSHOT feat(ControlPlaneAdapter): edr cache integration on TransferProcessStarted event (#328) * feat(ControlPlaneAdapter): edr cache integration on TransferProcessStarted event * trigger CI * removed seed from file feat: Data Plane extensions that implement DSP/AAS integration (#357) * Add DPF extensions * Updates and improcvements based on Paul's review feat(EdrManagementApi): open transfer refactor (#347) * feat(EdrManagementApi): refactor open transfer + dsp protocol switch * feat(EdrManagementApi): updates EDC to 0.0.1-milestone-9 --- .github/workflows/business-tests.yaml | 60 ++-- .github/workflows/verify.yaml | 2 - core/edr-cache-core/build.gradle.kts | 26 ++ .../edc/edr/core/EdrCacheCoreExtension.java | 46 +++ .../InMemoryEndpointDataReferenceCache.java | 99 ++++++ .../core/defaults/PersistentCacheEntry.java | 40 +++ ...rg.eclipse.edc.spi.system.ServiceExtension | 15 + ...nMemoryEndpointDataReferenceCacheTest.java | 63 ++++ .../defaults/PersistentCacheEntryTest.java | 53 +++ .../edc-controlplane-base/build.gradle.kts | 30 +- .../build.gradle.kts | 4 +- .../build.gradle.kts | 10 +- .../edc-runtime-memory/build.gradle.kts | 4 +- .../build.gradle.kts | 11 +- .../edc-dataplane-base/build.gradle.kts | 22 +- .../build.gradle.kts | 35 ++ .../DataPlaneProxyConsumerApiExtension.java | 109 ++++++ .../api/asset/ClientErrorExceptionMapper.java | 37 ++ .../api/asset/ConsumerAssetRequestApi.java | 38 ++ .../asset/ConsumerAssetRequestController.java | 155 +++++++++ .../asset/PreconditionFailedException.java | 29 ++ .../api/asset/model/AssetRequest.java | 85 +++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 13 + .../api/asset/model/AssetRequestTest.java | 51 +++ .../build.gradle.kts | 36 ++ .../DataPlaneProxyProviderApiExtension.java | 88 +++++ .../api/gateway/ProviderGatewayApi.java | 38 ++ .../gateway/ProviderGatewayController.java | 204 +++++++++++ .../provider/api/response/ResponseHelper.java | 44 +++ ...rg.eclipse.edc.spi.system.ServiceExtension | 13 + .../api/response/ResponseHelperTest.java | 32 ++ .../build.gradle.kts | 36 ++ .../core/ProxyProviderCoreExtension.java | 114 ++++++ .../AuthorizationHandlerRegistryImpl.java | 39 +++ .../gateway/auth/JwtAuthorizationHandler.java | 64 ++++ .../core/gateway/auth/RsaPublicKeyParser.java | 56 +++ .../GatewayConfigurationLoader.java | 46 +++ .../GatewayConfigurationRegistryImpl.java | 39 +++ ...rg.eclipse.edc.spi.system.ServiceExtension | 13 + .../AuthorizationHandlerRegistryImplTest.java | 32 ++ .../auth/JwtAuthorizationHandlerTest.java | 81 +++++ .../gateway/auth/RsaPublicKeyParserTest.java | 31 ++ .../core/gateway/auth/TestTokens.java | 43 +++ .../GatewayConfigurationLoaderTest.java | 52 +++ .../GatewayConfigurationRegistryImplTest.java | 31 ++ .../build.gradle.kts | 22 ++ .../authorization/AuthorizationExtension.java | 34 ++ .../authorization/AuthorizationHandler.java | 33 ++ .../AuthorizationHandlerRegistry.java | 35 ++ .../configuration/GatewayConfiguration.java | 81 +++++ .../GatewayConfigurationRegistry.java | 35 ++ .../build.gradle.kts | 8 +- .../BusinessPartnerValidationExtension.java | 4 +- ...AbstractBusinessPartnerValidationTest.java | 4 +- .../build.gradle.kts | 30 ++ .../api/cp/adapter/AdapterApiExtension.java | 50 +++ .../edc/api/cp/adapter/AdapterEdrApi.java | 42 +++ .../api/cp/adapter/AdapterEdrController.java | 68 ++++ .../adapter/dto/NegotiateEdrRequestDto.java | 123 +++++++ ...ctToNegotiateEdrRequestDtoTransformer.java | 89 +++++ ...tDtoToNegotiatedEdrRequestTransformer.java | 64 ++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 15 + .../adapter/AdapterEdrApiExtensionTest.java | 65 ++++ .../cp/adapter/AdapterEdrControllerTest.java | 113 ++++++ .../edc/api/cp/adapter/TestFunctions.java | 74 ++++ .../NegotiateEdrRequestDtoValidationTest.java | 42 +++ ...NegotiateEdrRequestDtoTransformerTest.java | 154 +++++++++ ...oToNegotiateEdrRequestTransformerTest.java | 96 ++++++ .../build.gradle.kts | 30 ++ .../AdapterTransferProcessServiceImpl.java | 65 ++++ .../callback/ContractNegotiationCallback.java | 81 +++++ .../InProcessCallbackMessageDispatcher.java | 55 +++ .../InProcessCallbackRegistryExtension.java | 37 ++ .../InProcessCallbackRegistryImpl.java | 43 +++ .../callback/LocalCallbackExtension.java | 92 +++++ .../TransferProcessLocalCallback.java | 77 +++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 16 + ...AdapterTransferProcessServiceImplTest.java | 90 +++++ .../ContractNegotiationCallbackTest.java | 152 ++++++++ ...nProcessCallbackMessageDispatcherTest.java | 79 +++++ ...nProcessCallbackRegistryExtensionTest.java | 41 +++ .../callback/LocalCallbackExtensionTest.java | 77 +++++ .../cp/adapter/callback/TestFunctions.java | 96 ++++++ .../TransferProcessLocalCallbackTest.java | 191 +++++++++++ .../control-plane-adapter/build.gradle.kts | 30 +- .../ContractAgreementRetriever.java | 87 +++-- .../ContractNegotiationHandler.java | 205 +++++------ .../ContractNegotiationListenerImpl.java | 104 +++--- .../ContractNotificationHandler.java | 109 +++--- .../DataTransferInitializer.java | 72 ++-- .../DataReferenceErrorHandler.java | 55 +-- .../CatalogRetrieverTest.java | 107 +++--- .../ContractAgreementRetrieverTest.java | 101 +++--- .../ContractNegotiationHandlerTest.java | 282 +++++++-------- .../ContractNegotiationListenerTest.java | 227 ++++++------ .../ContractNotificationHandlerTest.java | 222 ++++++------ .../DataReferenceErrorHandlerTest.java | 155 +++++---- edc-extensions/cx-oauth2/build.gradle.kts | 6 +- .../data-encryption/build.gradle.kts | 6 +- .../build.gradle.kts | 10 +- .../hashicorp-vault/build.gradle.kts | 6 +- .../build.gradle.kts | 12 +- .../postgresql-migration/build.gradle.kts | 10 +- ...lter_ContractNegotiation_Add_Callbacks.sql | 15 + ...7__Alter_TransferProcess_Add_Callbacks.sql | 15 + .../build.gradle.kts | 11 +- ...ovisionAdditionalHeadersExtensionTest.java | 118 ++++--- .../build.gradle.kts | 18 +- .../sftp/client/SftpDataSink.java | 31 +- .../sftp/client/SftpDataSource.java | 17 +- .../sftp/client/SftpDataSourceTest.java | 55 +-- .../build.gradle.kts | 4 +- .../build.gradle.kts | 8 +- edc-tests/cucumber/build.gradle.kts | 4 +- edc-tests/e2e-tests/build.gradle.kts | 26 +- .../edc/helpers/AssetHelperFunctions.java | 57 +++ .../edc/helpers/CatalogHelperFunctions.java | 71 ++++ .../ContractDefinitionHelperFunctions.java | 42 +++ .../ContractNegotiationHelperFunctions.java | 47 +++ .../EdrNegotiationHelperFunctions.java | 64 ++++ .../edc/helpers/PolicyHelperFunctions.java | 109 ++++++ .../edc/helpers/QueryHelperFunctions.java | 34 ++ .../TransferProcessHelperFunctions.java | 50 +++ .../tractusx/edc/lifecycle/DataWiper.java | 2 +- .../edc/lifecycle/MultiRuntimeTest.java | 40 ++- .../tractusx/edc/lifecycle/Participant.java | 299 ++++++++-------- .../lifecycle/TestRuntimeConfiguration.java | 14 +- .../edc/policy/PolicyHelperFunctions.java | 69 ---- .../tractusx/edc/tests/CatalogTest.java | 89 ++--- .../tests/HttpConsumerPullWithProxyTest.java | 50 +-- .../tractusx/edc/tests/NegotiateEdrTest.java | 164 +++++++++ .../edc-dataplane-proxy-e2e/build.gradle.kts | 37 ++ .../proxy/e2e/DpfProxyEndToEndTest.java | 193 +++++++++++ .../dataplane/proxy/e2e/EdrCacheSetup.java | 101 ++++++ .../dataplane/proxy/e2e/KeyStoreSetup.java | 45 +++ .../edc/dataplane/proxy/e2e/VaultSetup.java | 46 +++ edc-tests/runtime/build.gradle.kts | 2 +- gradle/libs.versions.toml | 132 +++++++ .../yaml/control-plane-adapter-api.yaml | 324 ++++++++++++++++++ .../yaml/observability-api-customization.yaml | 105 ++++++ settings.gradle.kts | 144 ++------ .../build.gradle.kts | 26 ++ .../adapter/callback/InProcessCallback.java | 28 ++ .../callback/InProcessCallbackRegistry.java | 43 +++ .../cp/adapter/model/NegotiateEdrRequest.java | 102 ++++++ .../AdapterTransferProcessService.java | 34 ++ spi/edr-cache-spi/build.gradle.kts | 22 ++ .../edr/spi/EndpointDataReferenceCache.java | 57 +++ .../edr/spi/EndpointDataReferenceEntry.java | 94 +++++ .../spi/EndpointDataReferenceEntryTest.java | 43 +++ version-catalog/build.gradle.kts | 33 ++ 151 files changed, 8081 insertions(+), 1531 deletions(-) create mode 100644 core/edr-cache-core/build.gradle.kts create mode 100644 core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/EdrCacheCoreExtension.java create mode 100644 core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCache.java create mode 100644 core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntry.java create mode 100644 core/edr-cache-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCacheTest.java create mode 100644 core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntryTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/build.gradle.kts create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ClientErrorExceptionMapper.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestApi.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/PreconditionFailedException.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequestTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/build.gradle.kts create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayApi.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelper.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelperTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/build.gradle.kts create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImpl.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImpl.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImplTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoaderTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImplTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/build.gradle.kts create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationExtension.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandler.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandlerRegistry.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfigurationRegistry.java create mode 100644 edc-extensions/control-plane-adapter-api/build.gradle.kts create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterApiExtension.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApi.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrController.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDto.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformer.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApiExtensionTest.java create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDtoValidationTest.java create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformerTest.java create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiateEdrRequestTransformerTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/build.gradle.kts create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImpl.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallback.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcher.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtension.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryImpl.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtension.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallback.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImplTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallbackTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcherTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtensionTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtensionTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TestFunctions.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallbackTest.java create mode 100644 edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_5__Alter_ContractNegotiation_Add_Callbacks.sql create mode 100644 edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_7__Alter_TransferProcess_Add_Callbacks.sql create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/AssetHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/CatalogHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractDefinitionHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractNegotiationHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/EdrNegotiationHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/QueryHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/TransferProcessHelperFunctions.java delete mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/NegotiateEdrTest.java create mode 100644 edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts create mode 100644 edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java create mode 100644 edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/EdrCacheSetup.java create mode 100644 edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/KeyStoreSetup.java create mode 100644 edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/VaultSetup.java create mode 100644 gradle/libs.versions.toml create mode 100644 resources/openapi/yaml/control-plane-adapter-api.yaml create mode 100644 resources/openapi/yaml/observability-api-customization.yaml create mode 100644 spi/control-plane-adapter-spi/build.gradle.kts create mode 100644 spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/callback/InProcessCallback.java create mode 100644 spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/callback/InProcessCallbackRegistry.java create mode 100644 spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/model/NegotiateEdrRequest.java create mode 100644 spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/service/AdapterTransferProcessService.java create mode 100644 spi/edr-cache-spi/build.gradle.kts create mode 100644 spi/edr-cache-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceCache.java create mode 100644 spi/edr-cache-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceEntry.java create mode 100644 spi/edr-cache-spi/src/test/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceEntryTest.java create mode 100644 version-catalog/build.gradle.kts diff --git a/.github/workflows/business-tests.yaml b/.github/workflows/business-tests.yaml index 1bc3436bc..d3f2aaab1 100644 --- a/.github/workflows/business-tests.yaml +++ b/.github/workflows/business-tests.yaml @@ -32,6 +32,7 @@ on: - releases - release/** - main + - previews/** workflow_dispatch: concurrency: @@ -49,26 +50,20 @@ jobs: ############## ### Set-Up ### ############## - - - uses: actions/checkout@v3.5.2 - - - uses: ./.github/actions/setup-java - - - name: Cache ContainerD Image Layers + - uses: actions/checkout@v3.5.2 + - uses: ./.github/actions/setup-java + - name: Cache ContainerD Image Layers uses: actions/cache@v3 with: path: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs key: ${{ runner.os }}-io.containerd.snapshotter.v1.overlayfs - - - name: Set-Up Kubectl + - name: Set-Up Kubectl uses: azure/setup-kubectl@v3.2 - - - name: Helm Set-Up + - name: Helm Set-Up uses: azure/setup-helm@v3.5 with: version: v3.8.1 - - - name: Create k8s Kind Cluster configuration (kind.config.yaml) + - name: Create k8s Kind Cluster configuration (kind.config.yaml) run: |- export MAVEN_REPOSITORY=${{ github.workspace }}/.m2/repository cat << EOF > kind.config.yaml @@ -85,8 +80,7 @@ jobs: - hostPath: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs containerPath: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs EOF - - - name: Create k8s Kind Cluster + - name: Create k8s Kind Cluster uses: helm/kind-action@v1.6.0 with: config: kind.config.yaml @@ -94,22 +88,19 @@ jobs: ############################################## ### Build and load recent images into KinD ### ############################################## - - - name: Build edc-controlplane-postgresql-hashicorp-vault + - name: Build edc-controlplane-postgresql-hashicorp-vault run: |- - ./gradlew :edc-controlplane:edc-controlplane-postgresql-hashicorp-vault:dockerize + ./gradlew :edc-controlplane:edc-controlplane-postgresql-hashicorp-vault:dockerize env: GITHUB_PACKAGE_USERNAME: ${{ github.actor }} GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - - - name: Build edc-dataplane-hashicorp-vault + - name: Build edc-dataplane-hashicorp-vault run: |- - ./gradlew :edc-dataplane:edc-dataplane-hashicorp-vault:dockerize + ./gradlew :edc-dataplane:edc-dataplane-hashicorp-vault:dockerize env: GITHUB_PACKAGE_USERNAME: ${{ github.actor }} GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - - - name: Load images into KinD + - name: Load images into KinD run: |- docker tag edc-controlplane-postgresql-hashicorp-vault:latest edc-controlplane-postgresql-hashicorp-vault:business-test docker tag edc-dataplane-hashicorp-vault:latest edc-dataplane-hashicorp-vault:business-test @@ -118,8 +109,7 @@ jobs: ############################################ ### Prepare And Install Test Environment ### ############################################ - - - name: Define test environment variables + - name: Define test environment variables run: |- # Define endpoints echo "SOKRATES_DATA_MANAGEMENT_API_AUTH_KEY=password" | tee -a ${GITHUB_ENV} @@ -141,8 +131,7 @@ jobs: echo "PLATO_AWS_ACCESS_KEY_ID=platoqwerty123" | tee -a ${GITHUB_ENV} echo "SOKRATES_AWS_SECRET_ACCESS_KEY=sokratesqwerty123" | tee -a ${GITHUB_ENV} echo "SOKRATES_AWS_ACCESS_KEY_ID=sokratesqwerty123" | tee -a ${GITHUB_ENV} - - - name: Install infrastructure components via Helm + - name: Install infrastructure components via Helm uses: nick-fields/retry@v2 with: timeout_minutes: 10 @@ -151,21 +140,21 @@ jobs: command: |- # Update helm dependencies helm dependency update edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure - + # Install the all-in-one supporting infrastructure environment (daps, vault, pgsql, minio) helm install infrastructure edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure \ --wait-for-jobs --timeout=120s - + # GH pipelines constrained by cpu, so give helm some time to register all resources \w k8s sleep 5s - + # Wait for supporting infrastructure to become ready (control-/data-plane, backend service) kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=idsdaps --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=idsdaps --tail 500 && exit 1 ) kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=vault --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=vault --tail 500 && exit 1 ) kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=sokrates-postgresql --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=sokrates-postgresql --tail 500 && exit 1 ) kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=plato-postgresql --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=plato-postgresql --tail 500 && exit 1 ) kubectl wait --for=condition=ready pod -l app=minio --timeout=120s || ( kubectl logs -l app=minio --tail 500 && exit 1 ) - + # Install Plato helm install plato charts/tractusx-connector \ --set fullnameOverride=plato \ @@ -233,10 +222,10 @@ jobs: --set dataplane.aws.accessKeyId=sokratesqwerty123 \ --set backendService.httpProxyTokenReceiverUrl=http://backend:8080 \ --wait-for-jobs --timeout=120s - + # GH pipelines constrained by cpu, so give helm some time to register all resources \w k8s sleep 5s - + # Wait for Control-/DataPlane and backend-service to become ready kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=sokrates-controlplane --timeout=600s || ( kubectl logs -l app.kubernetes.io/instance=sokrates-controlplane --tail 500 && exit 1 ) kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=sokrates-dataplane --timeout=600s || ( kubectl logs -l app.kubernetes.io/instance=sokrates-dataplane --tail 500 && exit 1 ) @@ -246,8 +235,8 @@ jobs: ############################################## ### Run Business Tests inside kind cluster ### ############################################## - - - name: Run Business Tests + - name: Run Business Tests + continue-on-error: true run: |- cat << EOF >> pod.json { @@ -332,8 +321,7 @@ jobs: ################# ### Tear Down ### ################# - - - name: Destroy the kind cluster + - name: Destroy the kind cluster if: always() run: >- kind get clusters | xargs -n1 kind delete cluster --name diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index c826f39b6..0eb686260 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -34,8 +34,6 @@ on: pull_request: paths-ignore: - 'charts/**' - branches: - - '*' workflow_dispatch: concurrency: diff --git a/core/edr-cache-core/build.gradle.kts b/core/edr-cache-core/build.gradle.kts new file mode 100644 index 000000000..0c1e5474d --- /dev/null +++ b/core/edr-cache-core/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + implementation(libs.edc.spi.core) + implementation(libs.edc.config.filesystem) + implementation(libs.edc.util) + + implementation(project(":spi:edr-cache-spi")) +} + diff --git a/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/EdrCacheCoreExtension.java b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/EdrCacheCoreExtension.java new file mode 100644 index 000000000..5de7e78a5 --- /dev/null +++ b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/EdrCacheCoreExtension.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.core; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.edr.core.defaults.InMemoryEndpointDataReferenceCache; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; + +/** + * Registers default services for the EDR cache. + */ +@Extension(value = EdrCacheCoreExtension.NAME) +public class EdrCacheCoreExtension implements ServiceExtension { + static final String NAME = "EDR Cache Core"; + + @Inject + private Monitor monitor; + + @Override + public String name() { + return NAME; + } + + @Provider(isDefault = true) + public EndpointDataReferenceCache edrCache(ServiceExtensionContext context) { + return new InMemoryEndpointDataReferenceCache(); + } + +} diff --git a/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCache.java b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCache.java new file mode 100644 index 000000000..d396b4170 --- /dev/null +++ b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCache.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.core.defaults; + +import org.eclipse.edc.spi.result.StoreResult; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.edc.util.concurrency.LockManager; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; +import static org.eclipse.edc.spi.result.StoreResult.notFound; +import static org.eclipse.edc.spi.result.StoreResult.success; + +/** + * An in-memory, threadsafe implementation of the cache. + */ +public class InMemoryEndpointDataReferenceCache implements EndpointDataReferenceCache { + private final LockManager lockManager; + + private final Map> entriesByAssetId; + private final Map entriesByEdrId; + private final Map edrsByTransferProcessId; + + public InMemoryEndpointDataReferenceCache() { + lockManager = new LockManager(new ReentrantReadWriteLock()); + entriesByAssetId = new HashMap<>(); + entriesByEdrId = new HashMap<>(); + edrsByTransferProcessId = new HashMap<>(); + } + + @Override + public @Nullable EndpointDataReference resolveReference(String transferProcessId) { + return lockManager.readLock(() -> edrsByTransferProcessId.get(transferProcessId)); + } + + @Override + @NotNull + public List referencesForAsset(String assetId) { + var entries = entriesByAssetId.get(assetId); + if (entries == null) { + return emptyList(); + } + return entries.stream().map(e -> resolveReference(e.getTransferProcessId())).filter(Objects::nonNull).collect(toList()); + } + + @Override + @NotNull + public List entriesForAsset(String assetId) { + return lockManager.readLock(() -> entriesByAssetId.getOrDefault(assetId, emptyList())); + } + + @Override + public void save(EndpointDataReferenceEntry entry, EndpointDataReference edr) { + lockManager.writeLock(() -> { + entriesByEdrId.put(edr.getId(), entry); + var list = entriesByAssetId.computeIfAbsent(entry.getAssetId(), k -> new ArrayList<>()); + list.add(entry); + + edrsByTransferProcessId.put(entry.getTransferProcessId(), edr); + return null; + }); + } + + @Override + public StoreResult deleteByTransferProcessId(String id) { + return lockManager.writeLock(() -> { + var edr = edrsByTransferProcessId.remove(id); + if (edr == null) { + return notFound("EDR entry not found for id: " + id); + } + var entry = entriesByEdrId.get(edr.getId()); + var entries = entriesByAssetId.get(entry.getAssetId()); + entries.remove(entry); + if (entries.isEmpty()) { + entriesByAssetId.remove(entry.getAssetId()); + } + return success(entry); + }); + } +} diff --git a/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntry.java b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntry.java new file mode 100644 index 000000000..3a9ae9ebd --- /dev/null +++ b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.core.defaults; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; + +/** + * A wrapper to persist {@link EndpointDataReferenceEntry}s and {@link EndpointDataReference}s. + */ +public class PersistentCacheEntry { + private EndpointDataReferenceEntry edrEntry; + private EndpointDataReference edr; + + public PersistentCacheEntry(@JsonProperty("edrEntry") EndpointDataReferenceEntry edrEntry, @JsonProperty("edr") EndpointDataReference edr) { + this.edrEntry = edrEntry; + this.edr = edr; + } + + public EndpointDataReferenceEntry getEdrEntry() { + return edrEntry; + } + + public EndpointDataReference getEdr() { + return edr; + } +} diff --git a/core/edr-cache-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/core/edr-cache-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..67f4a6027 --- /dev/null +++ b/core/edr-cache-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + +org.eclipse.tractusx.edc.edr.core.EdrCacheCoreExtension diff --git a/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCacheTest.java b/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCacheTest.java new file mode 100644 index 000000000..c2dab4afc --- /dev/null +++ b/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCacheTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.core.defaults; + +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; +import org.junit.jupiter.api.Test; + +import static java.util.UUID.randomUUID; +import static org.assertj.core.api.Assertions.assertThat; + +class InMemoryEndpointDataReferenceCacheTest { + private static final String TRANSFER_PROCESS_ID = "tp1"; + private static final String ASSET_ID = "asset1"; + private static final String EDR_ID = "edr1"; + + private InMemoryEndpointDataReferenceCache cache = new InMemoryEndpointDataReferenceCache(); + + @Test + @SuppressWarnings("DataFlowIssue") + void verify_operations() { + var edr = EndpointDataReference.Builder.newInstance(). + endpoint("http://test.com") + .id(EDR_ID) + .authCode("11111") + .authKey("authentication").build(); + + var entry = EndpointDataReferenceEntry.Builder.newInstance() + .assetId(ASSET_ID) + .agreementId(randomUUID().toString()) + .transferProcessId(TRANSFER_PROCESS_ID) + .build(); + + cache.save(entry, edr); + + assertThat(cache.resolveReference(TRANSFER_PROCESS_ID).getId()).isEqualTo(EDR_ID); + + var edrs = cache.referencesForAsset(ASSET_ID); + assertThat(edrs.size()).isEqualTo(1); + assertThat(edrs.get((0)).getId()).isEqualTo(EDR_ID); + + var entries = cache.entriesForAsset(ASSET_ID); + assertThat(entries.size()).isEqualTo(1); + assertThat(entries.get((0)).getAssetId()).isEqualTo(ASSET_ID); + + assertThat(cache.deleteByTransferProcessId(TRANSFER_PROCESS_ID).succeeded()).isTrue(); + + assertThat(cache.entriesForAsset(ASSET_ID)).isEmpty(); + assertThat(cache.resolveReference(TRANSFER_PROCESS_ID)).isNull(); + } +} diff --git a/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntryTest.java b/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntryTest.java new file mode 100644 index 000000000..764afb33b --- /dev/null +++ b/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntryTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.core.defaults; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; +import org.junit.jupiter.api.Test; + +import static java.util.UUID.randomUUID; +import static org.assertj.core.api.Assertions.assertThat; + +class PersistentCacheEntryTest { + + @Test + void verify_serializeDeserialize() throws JsonProcessingException { + var mapper = new ObjectMapper(); + + var edr = EndpointDataReference.Builder.newInstance(). + endpoint("http://test.com") + .id(randomUUID().toString()) + .authCode("11111") + .authKey("authentication").build(); + + var edrEntry = EndpointDataReferenceEntry.Builder.newInstance() + .assetId(randomUUID().toString()) + .agreementId(randomUUID().toString()) + .transferProcessId(randomUUID().toString()) + .build(); + + var serialized = mapper.writeValueAsString(new PersistentCacheEntry(edrEntry, edr)); + + var deserialized = mapper.readValue(serialized, PersistentCacheEntry.class); + + assertThat(deserialized.getEdrEntry()).isNotNull(); + assertThat(deserialized.getEdr()).isNotNull(); + } + + +} diff --git a/edc-controlplane/edc-controlplane-base/build.gradle.kts b/edc-controlplane/edc-controlplane-base/build.gradle.kts index 87dca3970..e868caedf 100644 --- a/edc-controlplane/edc-controlplane-base/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-base/build.gradle.kts @@ -23,6 +23,7 @@ plugins { } dependencies { + runtimeOnly(project(":core:edr-cache-core")) runtimeOnly(project(":edc-extensions:business-partner-validation")) runtimeOnly(project(":edc-extensions:dataplane-selector-configuration")) runtimeOnly(project(":edc-extensions:data-encryption")) @@ -30,18 +31,23 @@ dependencies { runtimeOnly(project(":edc-extensions:control-plane-adapter")) runtimeOnly(project(":edc-extensions:provision-additional-headers")) runtimeOnly(project(":edc-extensions:observability-api-customization")) + runtimeOnly(project(":edc-extensions:control-plane-adapter-api")) + runtimeOnly(project(":edc-extensions:control-plane-adapter-callback")) - runtimeOnly(edc.core.controlplane) - runtimeOnly(edc.config.filesystem) - runtimeOnly(edc.auth.tokenbased) - runtimeOnly(edc.auth.oauth2.core) - runtimeOnly(edc.auth.oauth2.daps) - runtimeOnly(edc.api.management) - runtimeOnly(edc.ids) - runtimeOnly(edc.spi.jwt) - runtimeOnly(edc.bundles.dpf) + runtimeOnly(libs.edc.core.controlplane) + runtimeOnly(libs.edc.config.filesystem) + runtimeOnly(libs.edc.auth.tokenbased) + runtimeOnly(libs.edc.auth.oauth2.core) + runtimeOnly(libs.edc.auth.oauth2.daps) + runtimeOnly(libs.edc.api.management) + runtimeOnly(libs.edc.dsp) + runtimeOnly(libs.edc.spi.jwt) + runtimeOnly(libs.bundles.edc.dpf) + + runtimeOnly(libs.edc.ext.http) + runtimeOnly(libs.bundles.edc.monitoring) + runtimeOnly(libs.edc.transfer.dynamicreceiver) + runtimeOnly(libs.edc.controlplane.callback.dispatcher.event) + runtimeOnly(libs.edc.controlplane.callback.dispatcher.http) - runtimeOnly(edc.ext.http) - runtimeOnly(edc.bundles.monitoring) - runtimeOnly(edc.transfer.dynamicreceiver) } diff --git a/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts index 0f69332a4..c6120480c 100644 --- a/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts @@ -27,8 +27,8 @@ plugins { dependencies { runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) runtimeOnly(project(":edc-extensions:hashicorp-vault")) - runtimeOnly(edc.core.controlplane) - runtimeOnly(edc.dpf.transfer) + runtimeOnly(libs.edc.core.controlplane) + runtimeOnly(libs.edc.dpf.transfer) } diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts index e760bc0c8..bdee99a82 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts @@ -30,11 +30,11 @@ dependencies { runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) runtimeOnly(project(":edc-extensions:postgresql-migration")) runtimeOnly(project(":edc-extensions:hashicorp-vault")) - runtimeOnly(edc.bundles.sqlstores) - runtimeOnly(edc.transaction.local) - runtimeOnly(edc.sql.pool) - runtimeOnly(edc.core.controlplane) - runtimeOnly(edc.dpf.transfer) + runtimeOnly(libs.bundles.edc.sqlstores) + runtimeOnly(libs.edc.transaction.local) + runtimeOnly(libs.edc.sql.pool) + runtimeOnly(libs.edc.core.controlplane) + runtimeOnly(libs.edc.dpf.transfer) } diff --git a/edc-controlplane/edc-runtime-memory/build.gradle.kts b/edc-controlplane/edc-runtime-memory/build.gradle.kts index b834ed12f..1df3d6915 100644 --- a/edc-controlplane/edc-runtime-memory/build.gradle.kts +++ b/edc-controlplane/edc-runtime-memory/build.gradle.kts @@ -24,12 +24,12 @@ plugins { } dependencies { - implementation(edc.spi.core) + implementation(libs.edc.spi.core) runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) { exclude(module = "data-encryption") } runtimeOnly(project(":edc-dataplane:edc-dataplane-base")) - runtimeOnly(edc.core.controlplane) + runtimeOnly(libs.edc.core.controlplane) } tasks.withType { diff --git a/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts index 1ae42a29a..134319535 100644 --- a/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts @@ -25,9 +25,14 @@ plugins { dependencies { implementation(project(":edc-dataplane:edc-dataplane-base")) - implementation(edc.azure.vault) - implementation(edc.azure.identity) - implementation("com.azure:azure-security-keyvault-secrets:4.6.1") + implementation(libs.edc.azure.vault) + constraints { + implementation("net.minidev:json-smart:2.4.10") { + because("version 2.4.8 has vulnerabilities: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1370.") + } + } + implementation(libs.edc.azure.identity) + implementation("com.azure:azure-security-keyvault-secrets:4.6.0") } tasks.withType { diff --git a/edc-dataplane/edc-dataplane-base/build.gradle.kts b/edc-dataplane/edc-dataplane-base/build.gradle.kts index af49bf16d..09d88a015 100644 --- a/edc-dataplane/edc-dataplane-base/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-base/build.gradle.kts @@ -25,16 +25,16 @@ plugins { dependencies { runtimeOnly(project(":edc-extensions:observability-api-customization")) - runtimeOnly(edc.config.filesystem) - runtimeOnly(edc.dpf.awss3) - runtimeOnly(edc.dpf.oauth2) - runtimeOnly(edc.dpf.http) + runtimeOnly(libs.edc.config.filesystem) + runtimeOnly(libs.edc.dpf.awss3) + runtimeOnly(libs.edc.dpf.oauth2) + runtimeOnly(libs.edc.dpf.http) - runtimeOnly(edc.dpf.framework) - runtimeOnly(edc.dpf.api) - runtimeOnly(edc.core.connector) - runtimeOnly(edc.boot) + runtimeOnly(libs.edc.dpf.framework) + runtimeOnly(libs.edc.dpf.api) + runtimeOnly(libs.edc.core.connector) + runtimeOnly(libs.edc.boot) - runtimeOnly(edc.bundles.monitoring) - runtimeOnly(edc.ext.http) -} \ No newline at end of file + runtimeOnly(libs.bundles.edc.monitoring) + runtimeOnly(libs.edc.ext.http) +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/build.gradle.kts b/edc-dataplane/edc-dataplane-proxy-consumer-api/build.gradle.kts new file mode 100644 index 000000000..6af73c993 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` + id("io.swagger.core.v3.swagger-gradle-plugin") +} + +dependencies { + + implementation(libs.jakarta.rsApi) + + implementation(libs.edc.spi.http) + implementation(libs.edc.util) + implementation(libs.edc.dpf.framework) + implementation(libs.edc.api.observability) + implementation(libs.edc.dpf.util) + implementation(libs.edc.ext.http) + + implementation(project(":spi:edr-cache-spi")) + + testImplementation(libs.edc.junit) +} + diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java new file mode 100644 index 000000000..566a63b7b --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api; + +import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebServer; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.eclipse.edc.web.spi.configuration.WebServiceSettings; +import org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.ConsumerAssetRequestController; +import org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.ClientErrorExceptionMapper; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; + +import java.util.concurrent.ExecutorService; + +import static java.util.concurrent.Executors.newFixedThreadPool; + +/** + * Instantiates the Proxy Data API for the consumer-side data plane. + */ +@Extension(value = DataPlaneProxyConsumerApiExtension.NAME) +public class DataPlaneProxyConsumerApiExtension implements ServiceExtension { + static final String NAME = "Data Plane Proxy Consumer API"; + + private static final int DEFAULT_PROXY_PORT = 8186; + private static final String CONSUMER_API_ALIAS = "consumer.api"; + private static final String CONSUMER_CONTEXT_PATH = "/proxy"; + private static final String CONSUMER_CONFIG_KEY = "web.http.proxy"; + + @Setting(value = "Data plane proxy API consumer port", type = "int") + private static final String CONSUMER_PORT = "tx.dpf.consumer.proxy.port"; + + @Setting(value = "Thread pool size for the consumer data plane proxy gateway", type = "int") + private static final String THREAD_POOL_SIZE = "tx.dpf.consumer.proxy.thread.pool"; + + public static final int DEFAULT_THREAD_POOL = 10; + + @Inject + private WebService webService; + + @Inject + private WebServer webServer; + + @Inject + private DataPlaneManager dataPlaneManager; + + @Inject + private EndpointDataReferenceCache edrCache; + + @Inject + private WebServiceConfigurer configurer; + + @Inject + private Monitor monitor; + + private ExecutorService executorService; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var port = context.getSetting(CONSUMER_PORT, DEFAULT_PROXY_PORT); + configurer.configure(context, webServer, createApiContext(port)); + + executorService = newFixedThreadPool(context.getSetting(THREAD_POOL_SIZE, DEFAULT_THREAD_POOL)); + + webService.registerResource(CONSUMER_API_ALIAS, new ClientErrorExceptionMapper()); + webService.registerResource(CONSUMER_API_ALIAS, new ConsumerAssetRequestController(edrCache, dataPlaneManager, executorService, monitor)); + } + + @Override + public void shutdown() { + if (executorService != null) { + executorService.shutdown(); + } + } + + private WebServiceSettings createApiContext(int port) { + return WebServiceSettings.Builder.newInstance() + .apiConfigKey(CONSUMER_CONFIG_KEY) + .contextAlias(CONSUMER_API_ALIAS) + .defaultPath(CONSUMER_CONTEXT_PATH) + .defaultPort(port) + .name(NAME) + .build(); + } + +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ClientErrorExceptionMapper.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ClientErrorExceptionMapper.java new file mode 100644 index 000000000..386428084 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ClientErrorExceptionMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset; + +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +/** + * Maps client errors to return the associated status. + */ +@Provider +public class ClientErrorExceptionMapper implements ExceptionMapper { + + public ClientErrorExceptionMapper() { + } + + @Override + public Response toResponse(ClientErrorException exception) { + return Response.status(exception.getResponse().getStatus()).build(); + } +} + + diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestApi.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestApi.java new file mode 100644 index 000000000..7902566aa --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestApi.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.model.AssetRequest; + +/** + * Defines the API for receiving asset requests on a consumer. + */ +@OpenAPIDefinition +@Tag(name = "Data Plane Proxy API") +public interface ConsumerAssetRequestApi { + + @Operation(responses = { + @ApiResponse(content = @Content(mediaType = "application/json", schema = @Schema(implementation = AssetRequest.class)), description = "Requests asset data") + }) + void requestAsset(AssetRequest request, @Suspended AsyncResponse response); +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java new file mode 100644 index 000000000..2ceb4a393 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset; + +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.core.StreamingOutput; +import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; +import org.eclipse.edc.connector.dataplane.util.sink.AsyncStreamingDataSink; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.edc.spi.types.domain.transfer.DataFlowRequest; +import org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.model.AssetRequest; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; + +import java.util.Map; +import java.util.concurrent.ExecutorService; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.BAD_GATEWAY; +import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static jakarta.ws.rs.core.Response.Status.NOT_FOUND; +import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED; +import static jakarta.ws.rs.core.Response.status; +import static java.lang.String.format; +import static java.util.UUID.randomUUID; + +/** + * Implements the HTTP proxy API. + */ +@Path("/aas") +public class ConsumerAssetRequestController implements ConsumerAssetRequestApi { + private static final String HTTP_DATA = "HttpData"; + private static final String ASYNC_TYPE = "async"; + private static final String BASE_URL = "baseUrl"; + private static final String HEADER_AUTHORIZATION = "header:authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + private final EndpointDataReferenceCache edrCache; + private final DataPlaneManager dataPlaneManager; + private final Monitor monitor; + + private final ExecutorService executorService; + + public ConsumerAssetRequestController(EndpointDataReferenceCache edrCache, + DataPlaneManager dataPlaneManager, + ExecutorService executorService, + Monitor monitor) { + this.edrCache = edrCache; + this.dataPlaneManager = dataPlaneManager; + this.executorService = executorService; + this.monitor = monitor; + } + + @POST + @Path("/request") + @Override + public void requestAsset(AssetRequest request, @Suspended AsyncResponse response) { + // resolve the EDR and add it to the request + var edr = resolveEdr(request); + + var sourceAddress = DataAddress.Builder.newInstance() + .type(HTTP_DATA) + .property(BASE_URL, request.getEndpointUrl()) + .property(HEADER_AUTHORIZATION, BEARER_PREFIX + edr.getAuthCode()) + .build(); + + var destinationAddress = DataAddress.Builder.newInstance() + .type(ASYNC_TYPE) + .build(); + + var flowRequest = DataFlowRequest.Builder.newInstance() + .processId(randomUUID().toString()) + .trackable(false) + .sourceDataAddress(sourceAddress) + .destinationDataAddress(destinationAddress) + .traceContext(Map.of()) + .build(); + + // transfer the data asynchronously + var sink = new AsyncStreamingDataSink(consumer -> response.resume((StreamingOutput) consumer::accept), executorService, monitor); + + try { + dataPlaneManager.transfer(sink, flowRequest).whenComplete((result, throwable) -> handleCompletion(response, result, throwable)); + } catch (Exception e) { + reportError(response, e); + } + } + + private EndpointDataReference resolveEdr(AssetRequest request) { + if (request.getTransferProcessId() != null) { + var edr = edrCache.resolveReference(request.getTransferProcessId()); + if (edr == null) { + throw new BadRequestException("No EDR for transfer process: " + request.getTransferProcessId()); + } + return edr; + } else { + var resolvedEdrs = edrCache.referencesForAsset(request.getAssetId()); + if (resolvedEdrs.isEmpty()) { + throw new BadRequestException("No EDR for asset: " + request.getAssetId()); + } else if (resolvedEdrs.size() > 1) { + throw new PreconditionFailedException("More than one EDR for asset: " + request.getAssetId()); + } + return resolvedEdrs.get(0); + } + } + + /** + * Handles a request completion, checking for errors. If no errors are present, nothing needs to be done as the response will have already been written to the client. + */ + private void handleCompletion(AsyncResponse response, StreamResult result, Throwable throwable) { + if (result != null && result.failed()) { + switch (result.reason()) { + case NOT_FOUND: + response.resume(status(NOT_FOUND).type(APPLICATION_JSON).build()); + break; + case NOT_AUTHORIZED: + response.resume(status(UNAUTHORIZED).type(APPLICATION_JSON).build()); + break; + case GENERAL_ERROR: + response.resume(status(INTERNAL_SERVER_ERROR).type(APPLICATION_JSON).build()); + break; + } + } else if (throwable != null) { + reportError(response, throwable); + } + } + + /** + * Reports an error to the client. On the consumer side, the error is reported as a {@code BAD_GATEWAY} since the consumer data plane acts as proxy. + */ + private void reportError(AsyncResponse response, Throwable throwable) { + monitor.severe("Error processing gateway request", throwable); + var entity = status(BAD_GATEWAY).entity(format("'%s'", throwable.getMessage())).type(APPLICATION_JSON).build(); + response.resume(entity); + } + +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/PreconditionFailedException.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/PreconditionFailedException.java new file mode 100644 index 000000000..24b865bf0 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/PreconditionFailedException.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset; + +import jakarta.ws.rs.ClientErrorException; + +import static jakarta.ws.rs.core.Response.Status.PRECONDITION_REQUIRED; + +/** + * Exception used to map a {@link jakarta.ws.rs.core.Response.Status#PRECONDITION_REQUIRED} response. + */ +public class PreconditionFailedException extends ClientErrorException { + + public PreconditionFailedException(String message) { + super(message, PRECONDITION_REQUIRED); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequest.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequest.java new file mode 100644 index 000000000..79fbf70dd --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import static java.util.Objects.requireNonNull; + +/** + * A request for asset data. The request may contain a transfer process ID or asset ID and must specify an endpoint for retrieving the data. + */ +@JsonDeserialize(builder = AssetRequest.Builder.class) +@JsonTypeName("tx:assetrequest") +public class AssetRequest { + private String transferProcessId; + private String assetId; + private String endpointUrl; + + public String getTransferProcessId() { + return transferProcessId; + } + + public String getAssetId() { + return assetId; + } + + public String getEndpointUrl() { + return endpointUrl; + } + + private AssetRequest() { + } + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + private final AssetRequest request; + + @JsonCreator + public static Builder newInstance() { + return new Builder(); + } + + public Builder transferProcessId(String transferProcessId) { + request.transferProcessId = transferProcessId; + return this; + } + + public Builder assetId(String assetId) { + request.assetId = assetId; + return this; + } + + public Builder endpointUrl(String endpointUrl) { + request.endpointUrl = endpointUrl; + return this; + } + + public AssetRequest build() { + if (request.assetId == null && request.transferProcessId == null) { + throw new NullPointerException("An assetId or endpointReferenceId must be set"); + } + requireNonNull(request.endpointUrl, "endpointUrl"); + return request; + } + + private Builder() { + request = new AssetRequest(); + } + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..11229c1f5 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,13 @@ + # + # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + # + # This program and the accompanying materials are made available under the + # terms of the Apache License, Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # + # Contributors: + # Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + + org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.DataPlaneProxyConsumerApiExtension diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequestTest.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequestTest.java new file mode 100644 index 000000000..5855b20dd --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequestTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.model; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class AssetRequestTest { + + @Test + void verify_SerializeDeserialize() throws JsonProcessingException { + var mapper = new ObjectMapper(); + + var request = AssetRequest.Builder.newInstance().assetId("asset1").endpointUrl("https://test.com").transferProcessId("tp1").build(); + var serialized = mapper.writeValueAsString(request); + + var deserialized = mapper.readValue(serialized, AssetRequest.class); + + assertThat(deserialized.getAssetId()).isEqualTo(request.getAssetId()); + assertThat(deserialized.getTransferProcessId()).isEqualTo(request.getTransferProcessId()); + assertThat(deserialized.getEndpointUrl()).isEqualTo(request.getEndpointUrl()); + } + + @Test + void verify_NullArguments() { + assertThatThrownBy(() -> AssetRequest.Builder.newInstance().endpointUrl("https://test.com").build()).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> AssetRequest.Builder.newInstance().assetId("asset1").build()).isInstanceOf(NullPointerException.class); + } + + @Test + void verify_AssetIdOrTransferProcessId() { + AssetRequest.Builder.newInstance().assetId("asset1").endpointUrl("https://test.com").build(); + AssetRequest.Builder.newInstance().transferProcessId("tp1").endpointUrl("https://test.com").build(); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/build.gradle.kts b/edc-dataplane/edc-dataplane-proxy-provider-api/build.gradle.kts new file mode 100644 index 000000000..37aabac01 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` + id("io.swagger.core.v3.swagger-gradle-plugin") +} + +dependencies { + + implementation(libs.edc.spi.http) + implementation(libs.edc.util) + implementation(libs.edc.dpf.framework) + implementation(libs.edc.api.observability) + implementation(libs.edc.dpf.util) + implementation(libs.edc.ext.http) + implementation(libs.edc.spi.jwt) + implementation(libs.edc.jwt.core) + + implementation(libs.jakarta.rsApi) + implementation(libs.nimbus.jwt) + + implementation(project(":edc-dataplane:edc-dataplane-proxy-provider-spi")) +} + diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java new file mode 100644 index 000000000..6e845090c --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api; + +import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.api.gateway.ProviderGatewayController; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfigurationRegistry; + +import java.util.concurrent.ExecutorService; + +import static java.util.concurrent.Executors.newFixedThreadPool; + +/** + * Adds the consumer proxy data plane API. + */ +@Extension(value = DataPlaneProxyProviderApiExtension.NAME) +public class DataPlaneProxyProviderApiExtension implements ServiceExtension { + static final String NAME = "Data Plane Proxy Provider API"; + + @Setting(value = "Thread pool size for the provider data plane proxy gateway", type = "int") + private static final String THREAD_POOL_SIZE = "tx.dpf.provider.proxy.thread.pool"; + + public static final int DEFAULT_THREAD_POOL = 10; + + @Inject + private WebService webService; + + @Inject + private DataPlaneManager dataPlaneManager; + + @Inject + private Monitor monitor; + + @Inject + private GatewayConfigurationRegistry configurationRegistry; + + @Inject + private AuthorizationHandlerRegistry authorizationRegistry; + + private ExecutorService executorService; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + executorService = newFixedThreadPool(context.getSetting(THREAD_POOL_SIZE, DEFAULT_THREAD_POOL)); + + var controller = new ProviderGatewayController(dataPlaneManager, + configurationRegistry, + authorizationRegistry, + executorService, + monitor); + + webService.registerResource(controller); + } + + + @Override + public void shutdown() { + if (executorService != null) { + executorService.shutdown(); + } + } + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayApi.java b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayApi.java new file mode 100644 index 000000000..f951123b3 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayApi.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api.gateway; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.core.Context; + +/** + * Open API definition. + */ +@OpenAPIDefinition +@Tag(name = "Data Plane Proxy API") +public interface ProviderGatewayApi { + + @Operation(responses = { + @ApiResponse(content = @Content(mediaType = "application/json"), description = "Gets asset data") + }) + void requestAsset(@Context ContainerRequestContext context, @Suspended AsyncResponse response); +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java new file mode 100644 index 000000000..2f392fdce --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api.gateway; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.PathSegment; +import jakarta.ws.rs.core.StreamingOutput; +import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; +import org.eclipse.edc.connector.dataplane.util.sink.AsyncStreamingDataSink; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.transfer.DataFlowRequest; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfigurationRegistry; + +import java.util.Map; +import java.util.concurrent.ExecutorService; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST; +import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static jakarta.ws.rs.core.Response.Status.NOT_FOUND; +import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED; +import static jakarta.ws.rs.core.Response.status; +import static java.lang.String.format; +import static java.util.UUID.randomUUID; +import static java.util.stream.Collectors.joining; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.api.response.ResponseHelper.createMessageResponse; + +/** + * Implements the HTTP data proxy API. + */ +@Path("/" + ProviderGatewayController.GATEWAY_PATH) +public class ProviderGatewayController implements ProviderGatewayApi{ + protected static final String GATEWAY_PATH = "gateway"; + + private static final String HTTP_DATA = "HttpData"; + private static final String BASE_URL = "baseUrl"; + private static final String ASYNC = "async"; + + private static final int ALIAS_SEGMENT = 1; + private static final String BEARER_PREFIX = "Bearer "; + + private final DataPlaneManager dataPlaneManager; + private final GatewayConfigurationRegistry configurationRegistry; + private final AuthorizationHandlerRegistry authorizationRegistry; + + private final Monitor monitor; + + private final ExecutorService executorService; + + public ProviderGatewayController(DataPlaneManager dataPlaneManager, + GatewayConfigurationRegistry configurationRegistry, + AuthorizationHandlerRegistry authorizationRegistry, + ExecutorService executorService, + Monitor monitor) { + this.dataPlaneManager = dataPlaneManager; + this.configurationRegistry = configurationRegistry; + this.authorizationRegistry = authorizationRegistry; + this.executorService = executorService; + this.monitor = monitor; + } + + @GET + @Path("/{paths: .+}") + @Override + public void requestAsset(@Context ContainerRequestContext context, @Suspended AsyncResponse response) { + var tokens = context.getHeaders().get(HttpHeaders.AUTHORIZATION); + if (tokens == null || tokens.isEmpty()) { + response.resume(createMessageResponse(UNAUTHORIZED, "No bearer token", context.getMediaType())); + return; + } + var token = tokens.get(0); + if (!token.startsWith(BEARER_PREFIX)) { + response.resume(createMessageResponse(UNAUTHORIZED, "Invalid bearer token", context.getMediaType())); + return; + } else { + token = token.substring(BEARER_PREFIX.length()); + } + + var uriInfo = context.getUriInfo(); + var segments = uriInfo.getPathSegments(); + if (segments.size() < 3 || !GATEWAY_PATH.equals(segments.get(0).getPath())) { + response.resume(createMessageResponse(BAD_REQUEST, "Invalid path", context.getMediaType())); + return; + } + + var alias = segments.get(ALIAS_SEGMENT).getPath(); + var configuration = configurationRegistry.getConfiguration(alias); + if (configuration == null) { + response.resume(createMessageResponse(NOT_FOUND, "Unknown path", context.getMediaType())); + return; + } + + // calculate the sub-path, which all segments after the GATEWAY segment, including the alias segment + var subPath = segments.stream().skip(1).map(PathSegment::getPath).collect(joining("/")); + if (!authenticate(token, configuration.getAuthorizationType(), subPath, context, response)) { + return; + } + + // calculate the request path, which all segments after the alias segment + var requestPath = segments.stream().skip(2).map(PathSegment::getPath).collect(joining("/")); + var flowRequest = createRequest(requestPath, configuration); + + // transfer the data asynchronously + var sink = new AsyncStreamingDataSink(consumer -> response.resume((StreamingOutput) consumer::accept), executorService, monitor); + + try { + dataPlaneManager.transfer(sink, flowRequest).whenComplete((result, throwable) -> handleCompletion(response, result, throwable)); + } catch (Exception e) { + reportError(response, e); + } + } + + private DataFlowRequest createRequest(String subPath, GatewayConfiguration configuration) { + var path = configuration.getProxiedPath() + "/" + subPath; + + var sourceAddress = DataAddress.Builder.newInstance() + .type(HTTP_DATA) + .property(BASE_URL, path) + .build(); + + var destinationAddress = DataAddress.Builder.newInstance() + .type(ASYNC) + .build(); + + return DataFlowRequest.Builder.newInstance() + .processId(randomUUID().toString()) + .trackable(false) + .sourceDataAddress(sourceAddress) + .destinationDataAddress(destinationAddress) + .traceContext(Map.of()) + .build(); + } + + private boolean authenticate(String token, String authType, String subPath, ContainerRequestContext context, AsyncResponse response) { + var handler = authorizationRegistry.getHandler(authType); + if (handler == null) { + var correlationId = randomUUID().toString(); + monitor.severe(format("Authentication handler not configured for type: %s [id: %s]", authType, correlationId)); + response.resume(createMessageResponse(INTERNAL_SERVER_ERROR, format("Internal server error: %s", correlationId), context.getMediaType())); + return false; + } + + var authResponse = handler.authorize(token, subPath); + if (authResponse.failed()) { + response.resume(status(UNAUTHORIZED).build()); + return false; + } + return true; + } + + /** + * Handles a request completion, checking for errors. If no errors are present, nothing needs to be done as the response will have already been written to the client. + */ + private void handleCompletion(AsyncResponse response, StreamResult result, Throwable throwable) { + if (result != null && result.failed()) { + switch (result.reason()) { + case NOT_FOUND: + response.resume(status(NOT_FOUND).type(APPLICATION_JSON).build()); + break; + case NOT_AUTHORIZED: + response.resume(status(UNAUTHORIZED).type(APPLICATION_JSON).build()); + break; + case GENERAL_ERROR: + response.resume(status(INTERNAL_SERVER_ERROR).type(APPLICATION_JSON).build()); + break; + } + } else if (throwable != null) { + reportError(response, throwable); + } + } + + /** + * Reports an error to the client. On the provider side, the error is reported as a {@code INTERNAL_SERVER_ERROR} since the provider data plane is considered an origin server + * even though it may delegate requests to other internal sources. + */ + private void reportError(AsyncResponse response, Throwable throwable) { + monitor.severe("Error processing gateway request", throwable); + var entity = status(INTERNAL_SERVER_ERROR).entity(format("'%s'", throwable.getMessage())).type(APPLICATION_JSON).build(); + response.resume(entity); + } + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelper.java b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelper.java new file mode 100644 index 000000000..816c7a446 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelper.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api.response; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.jetbrains.annotations.Nullable; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN; +import static jakarta.ws.rs.core.Response.status; +import static java.lang.String.format; + +/** + * Utility functions for creating responses. + */ +public class ResponseHelper { + + /** + * Creates a response with a message encoded for the given media type. Currently, {@code APPLICATION_JSON} and {@code TEXT_PLAIN} are supported. + */ + public static Response createMessageResponse(Response.Status status, String message, @Nullable MediaType mediaType) { + if (mediaType != null && APPLICATION_JSON.equals(mediaType.toString())) { + return status(status).entity(format("'%s'", message)).type(APPLICATION_JSON).build(); + } else { + return status(status).entity(format("%s", message)).type(TEXT_PLAIN).build(); + } + } + + private ResponseHelper() { + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..be66cb9c7 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,13 @@ + # + # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + # + # This program and the accompanying materials are made available under the + # terms of the Apache License, Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # + # Contributors: + # Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + +org.eclipse.tractusx.edc.dataplane.proxy.provider.api.DataPlaneProxyProviderApiExtension diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelperTest.java b/edc-dataplane/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelperTest.java new file mode 100644 index 000000000..f9abd0327 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelperTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api.response; + +import org.junit.jupiter.api.Test; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN_TYPE; +import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.api.response.ResponseHelper.createMessageResponse; + +class ResponseHelperTest { + + @Test + void verify_responses() { + assertThat(createMessageResponse(INTERNAL_SERVER_ERROR, "Some error", APPLICATION_JSON_TYPE).getEntity()).isEqualTo("'Some error'"); + assertThat(createMessageResponse(INTERNAL_SERVER_ERROR, "Some error", TEXT_PLAIN_TYPE).getEntity()).isEqualTo("Some error"); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/build.gradle.kts b/edc-dataplane/edc-dataplane-proxy-provider-core/build.gradle.kts new file mode 100644 index 000000000..fae93ad79 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + + implementation(libs.edc.util) + implementation(libs.edc.dpf.framework) + implementation(libs.edc.api.observability) + implementation(libs.edc.dpf.util) + implementation(libs.edc.jwt.core) + implementation(libs.edc.ext.http) + implementation(libs.edc.spi.http) + + implementation(libs.edc.spi.jwt) + + implementation(libs.jakarta.rsApi) + implementation(libs.nimbus.jwt) + + implementation(project(":edc-dataplane:edc-dataplane-proxy-provider-spi")) +} + diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java new file mode 100644 index 000000000..5f8dd66e2 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core; + +import com.nimbusds.jose.crypto.RSASSAVerifier; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth.AuthorizationHandlerRegistryImpl; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth.JwtAuthorizationHandler; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth.RsaPublicKeyParser; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationRegistryImpl; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationExtension; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandler; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfigurationRegistry; +import org.jetbrains.annotations.NotNull; + +import static java.lang.String.format; +import static org.eclipse.edc.spi.result.Result.failure; +import static org.eclipse.edc.spi.result.Result.success; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationLoader.loadConfiguration; +import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.NO_AUTHORIZATION; +import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.TOKEN_AUTHORIZATION; + +/** + * Registers default services for the data plane provider proxy implementation. + */ +@Extension(value = ProxyProviderCoreExtension.NAME) +@Provides({GatewayConfigurationRegistry.class, AuthorizationHandlerRegistry.class}) +public class ProxyProviderCoreExtension implements ServiceExtension { + static final String NAME = "Data Plane Provider Proxy Core"; + + @Setting + private static final String PUBLIC_KEY = "tx.dpf.data.proxy.public.key"; + + @Inject(required = false) + private AuthorizationExtension authorizationExtension; + + @Inject + private Vault vault; + + @Inject + private Monitor monitor; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var configurationRegistry = new GatewayConfigurationRegistryImpl(); + context.registerService(GatewayConfigurationRegistry.class, configurationRegistry); + + if (authorizationExtension == null) { + context.getMonitor().info("Proxy JWT authorization is configured to only validate tokens and not provide path access control"); + authorizationExtension = (c, p) -> success(); + } + + var authorizationRegistry = creatAuthorizationRegistry(); + context.registerService(AuthorizationHandlerRegistry.class, authorizationRegistry); + + loadConfiguration(context).forEach(configuration -> { + monitor.info(format("Registering gateway configuration alias `%s` to %s", configuration.getAlias(), configuration.getProxiedPath())); + configurationRegistry.register(configuration); + }); + } + + @NotNull + private AuthorizationHandlerRegistryImpl creatAuthorizationRegistry() { + var authorizationRegistry = new AuthorizationHandlerRegistryImpl(); + + authorizationRegistry.register(NO_AUTHORIZATION, (t, p) -> success()); + + authorizationRegistry.register(TOKEN_AUTHORIZATION, createJwtAuthorizationHandler()); + + return authorizationRegistry; + } + + @NotNull + private AuthorizationHandler createJwtAuthorizationHandler() { + var publicCertKey = vault.resolveSecret(PUBLIC_KEY); + + if (publicCertKey == null) { + monitor.warning("Data proxy public key not set in the vault. Disabling JWT authorization for the proxy data."); + return (t, p) -> failure("Authentication disabled"); + } + + var publicKey = new RsaPublicKeyParser().parsePublicKey(publicCertKey); + var verifier = new RSASSAVerifier(publicKey); + + return new JwtAuthorizationHandler(verifier, authorizationExtension, monitor); + } + + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImpl.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImpl.java new file mode 100644 index 000000000..8c1878c73 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandler; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Default implementation of the registry. + */ +public class AuthorizationHandlerRegistryImpl implements AuthorizationHandlerRegistry { + private final Map handlers = new HashMap<>(); + + @Override + public @Nullable AuthorizationHandler getHandler(String alias) { + return handlers.get(alias); + } + + @Override + public void register(String alias, AuthorizationHandler handler) { + handlers.put(alias, handler); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java new file mode 100644 index 000000000..a4d1ca315 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationExtension; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandler; + +import java.text.ParseException; + +import static org.eclipse.edc.spi.result.Result.failure; + +/** + * Authenticates JWTs using a provided verifier and delegates to an {@link AuthorizationExtension} to provide access control checks for the requested path. + */ +public class JwtAuthorizationHandler implements AuthorizationHandler { + private final JWSVerifier verifier; + private final AuthorizationExtension authorizationExtension; + private final Monitor monitor; + + public JwtAuthorizationHandler(JWSVerifier verifier, AuthorizationExtension authorizationExtension, Monitor monitor) { + this.verifier = verifier; + this.authorizationExtension = authorizationExtension; + this.monitor = monitor; + } + + @Override + public Result authorize(String token, String path) { + try { + var jwt = SignedJWT.parse(token); + var result = jwt.verify(verifier); + + if (!result) { + return failure("Invalid token"); + } + + var claimToken = ClaimToken.Builder.newInstance() + .claims(jwt.getJWTClaimsSet().getClaims()) + .build(); + + return authorizationExtension.authorize(claimToken, path); + } catch (ParseException | JOSEException e) { + monitor.info("Invalid JWT received: " + e.getMessage()); + return failure("Invalid token"); + } + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java new file mode 100644 index 000000000..23d0a2af9 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import org.eclipse.edc.spi.EdcException; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + * A thread-safe parser than can read RSA public keys stored using PEM encoding. + */ +public class RsaPublicKeyParser { + private static final String HEADER = "-----BEGIN PUBLIC KEY-----"; + private static final String FOOTER = "-----END PUBLIC KEY-----"; + private final KeyFactory keyFactory; + + public RsaPublicKeyParser() { + try { + keyFactory = KeyFactory.getInstance("RSA"); + } catch (NoSuchAlgorithmException e) { + throw new EdcException(e); + } + } + + /** + * Parses the PEM-encoded key. + */ + public RSAPublicKey parsePublicKey(String serialized) { + var keyPortion = serialized.replace(HEADER, "").replace(FOOTER, "").replaceAll("\\s", ""); + + var publicKeyDer = Base64.getDecoder().decode(keyPortion); + var spec = new X509EncodedKeySpec(publicKeyDer); + try { + return (RSAPublicKey) keyFactory.generatePublic(spec); + } catch (InvalidKeySpecException e) { + throw new EdcException(e); + } + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java new file mode 100644 index 000000000..430b1c38a --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration; + +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.Config; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration; + +import java.util.List; + +import static java.util.stream.Collectors.toList; +import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.TOKEN_AUTHORIZATION; + +/** + * Loads gateway configuration from the {@link #TX_GATEWAY_PREFIX} prefix. + */ +public class GatewayConfigurationLoader { + static final String TX_GATEWAY_PREFIX = "tx.dpf.proxy.gateway"; + static final String AUTHORIZATION_TYPE = "authorization.type"; + static final String PROXIED_PATH = "proxied.path"; + + public static List loadConfiguration(ServiceExtensionContext context) { + var root = context.getConfig(TX_GATEWAY_PREFIX); + return root.partition().map(GatewayConfigurationLoader::createGatewayConfiguration).collect(toList()); + } + + private static GatewayConfiguration createGatewayConfiguration(Config config) { + return GatewayConfiguration.Builder.newInstance() + .alias(config.currentNode()) + .authorizationType(config.getString(AUTHORIZATION_TYPE, TOKEN_AUTHORIZATION)) + .proxiedPath(config.getString(PROXIED_PATH)) + .build(); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImpl.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImpl.java new file mode 100644 index 000000000..45a5b8d91 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration; + +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfigurationRegistry; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Default implementation. + */ +public class GatewayConfigurationRegistryImpl implements GatewayConfigurationRegistry { + private final Map configurations = new HashMap<>(); + + @Override + public @Nullable GatewayConfiguration getConfiguration(String alias) { + return configurations.get(alias); + } + + @Override + public void register(GatewayConfiguration configuration) { + configurations.put(configuration.getAlias(), configuration); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..5153c83eb --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,13 @@ + # + # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + # + # This program and the accompanying materials are made available under the + # terms of the Apache License, Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # + # Contributors: + # Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + +org.eclipse.tractusx.edc.dataplane.proxy.provider.core.ProxyProviderCoreExtension diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImplTest.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImplTest.java new file mode 100644 index 000000000..346481a55 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImplTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandler; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class AuthorizationHandlerRegistryImplTest { + + @Test + void verify_registration() { + var registry = new AuthorizationHandlerRegistryImpl(); + registry.register("alias", mock(AuthorizationHandler.class)); + + assertThat(registry.getHandler("alias")).isNotNull(); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java new file mode 100644 index 000000000..9b74fe54d --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSVerifier; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.spi.result.Result.failure; +import static org.eclipse.edc.spi.result.Result.success; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class JwtAuthorizationHandlerTest { + private JwtAuthorizationHandler handler; + private AuthorizationExtension authExtension; + private JWSVerifier verifier; + + + @BeforeEach + void setUp() { + verifier = mock(JWSVerifier.class); + Monitor monitor = mock(Monitor.class); + authExtension = mock(AuthorizationExtension.class); + handler = new JwtAuthorizationHandler(verifier, authExtension, monitor); + } + + @Test + void verify_validCase() throws JOSEException { + when(verifier.verify(any(), any(), any())).thenReturn(true); + when(authExtension.authorize(isA(ClaimToken.class), eq("foo"))).thenReturn(success()); + + var result = handler.authorize(TestTokens.TEST_TOKEN, "foo"); + + assertThat(result.succeeded()).isTrue(); + } + + @Test + void verify_parseInValidToken() throws JOSEException { + when(verifier.verify(any(), any(), any())).thenReturn(false); + + var result = handler.authorize(TestTokens.TEST_TOKEN, "foo"); + + assertThat(result.succeeded()).isFalse(); + } + + @Test + void verify_notAuthorized() throws JOSEException { + when(verifier.verify(any(), any(), any())).thenReturn(true); + when(authExtension.authorize(isA(ClaimToken.class), eq("foo"))).thenReturn(failure("Not authorized")); + + var result = handler.authorize(TestTokens.TEST_TOKEN, "foo"); + + assertThat(result.succeeded()).isFalse(); + + verify(authExtension).authorize(isA(ClaimToken.class), eq("foo")); + } + + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java new file mode 100644 index 000000000..d345ce388 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Verifies RSA public key parsing. + */ +class RsaPublicKeyParserTest { + + @Test + void verify_canParseKey() { + var key = new RsaPublicKeyParser().parsePublicKey(TestTokens.generatePublic()); + assertNotNull(key); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java new file mode 100644 index 000000000..8db44bee8 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +/** + * Tokens for testing. + */ +public class TestTokens { + private static final String DELIMITER = "-----"; + private static final String HEADER = DELIMITER + "BEGIN" + " PUBLIC " + "KEY" + DELIMITER + "\n"; + private static final String FOOTER = "\n" + DELIMITER + "END" + " PUBLIC " + "KEY" + DELIMITER + "\n"; + + public static final String TEST_TOKEN = "eyJhbGciOiJSUzI1NiIsInZlcnNpb24iOnRydWV9.eyJpc3MiOiJ0ZXN0LWNvbm5lY3RvciIsInN1YiI6ImNvbnN1bWVyLWNvbm5lY3RvciIsImF1ZCI6InRlc3QtY29ubmVjdG9yIiwiaWF0IjoxNjgxOTEzNjM2LCJleHAiOjMzNDU5NzQwNzg4LCJjaWQiOiIzMmE2M2E3ZC04MGQ2LTRmMmUtOTBlNi04MGJhZjVmYzJiM2MifQ.QAuotoRxpEqfuzkTcTq2w5Tcyy3Rc3UzUjjvNc_zwgNROGLe-wO9tFET1dJ_I5BttRxkngDS37dS4R6lN5YXaGHgcH2rf_FuVcJUSFqTp_usGAcx6m7pQQwqpNdcYgmq0NJp3xP87EFPHAy4kBxB5bqpmx4J-zrj9U_gerZ2WlRqpu0SdgP0S5v5D1Gm-vYkLqgvsugrAWH3Ti7OjC5UMdj0kDFwro2NpMY8SSNryiVvBEv8hn0KZdhhebIqPdhqbEQZ9d8WKzcgoqQ3DBd4ijzkd3Fz7ADD2gy_Hxn8Hi2LcItuB514TjCxYAncTNqZC_JSFEyuxwcGFVz3LdSXgw"; + + + public static String generatePublic() { + try { + var generator = KeyPairGenerator.getInstance("RSA"); + var pair = generator.generateKeyPair(); + var encoded = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()); + return HEADER + encoded + FOOTER; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoaderTest.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoaderTest.java new file mode 100644 index 000000000..b9eec317f --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoaderTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration; + +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationLoader.AUTHORIZATION_TYPE; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationLoader.PROXIED_PATH; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationLoader.TX_GATEWAY_PREFIX; +import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.NO_AUTHORIZATION; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class GatewayConfigurationLoaderTest { + + @Test + void verify_loadConfiguration() { + var context = mock(ServiceExtensionContext.class); + + var config = ConfigFactory.fromMap( + Map.of(format("alias.%s", AUTHORIZATION_TYPE), NO_AUTHORIZATION, + format("alias.%s", PROXIED_PATH), "https://test.com")); + when(context.getConfig(TX_GATEWAY_PREFIX)).thenReturn(config); + + var configurations = GatewayConfigurationLoader.loadConfiguration(context); + + assertThat(configurations).isNotEmpty(); + var configuration = configurations.get(0); + + assertThat(configuration.getAlias()).isEqualTo("alias"); + assertThat(configuration.getAuthorizationType()).isEqualTo(NO_AUTHORIZATION); + assertThat(configuration.getProxiedPath()).isEqualTo("https://test.com"); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImplTest.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImplTest.java new file mode 100644 index 000000000..9bd58c365 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImplTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration; + +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class GatewayConfigurationRegistryImplTest { + + @Test + void verify_Configuration() { + var registry = new GatewayConfigurationRegistryImpl(); + registry.register(GatewayConfiguration.Builder.newInstance().proxiedPath("https://test.com").alias("alias").build()); + + assertThat(registry.getConfiguration("alias")).isNotNull(); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/build.gradle.kts b/edc-dataplane/edc-dataplane-proxy-provider-spi/build.gradle.kts new file mode 100644 index 000000000..9ca2f9437 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + implementation(libs.edc.spi.core) +} + diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationExtension.java b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationExtension.java new file mode 100644 index 000000000..37ffc5490 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationExtension.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization; + +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.result.Result; + +/** + * Performs an authorization check for the given path against a set of claims. + */ +public interface AuthorizationExtension { + + /** + * Performs an authorization check for the given path against the presented claims. The path is the request alias path, not + * the proxied path. + * + * @param token the validated claim token + * @param path the request alias path, not the dereferenced proxied path + */ + Result authorize(ClaimToken token, String path); + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandler.java b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandler.java new file mode 100644 index 000000000..5442ebc98 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandler.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization; + +import org.eclipse.edc.spi.result.Result; + +/** + * Performs an authorization using the request token for a given path. Implementation support different token formats such as JWT. + */ +@FunctionalInterface +public interface AuthorizationHandler { + + /** + * Performs the authorization check. + * + * @param token the unvalidated token + * @param path the request alias path, not the dereferenced proxied path + */ + Result authorize(String token, String path); + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandlerRegistry.java b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandlerRegistry.java new file mode 100644 index 000000000..b40217cb4 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandlerRegistry.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization; + +import org.jetbrains.annotations.Nullable; + +/** + * Manages {@link AuthorizationHandler}s. + */ +public interface AuthorizationHandlerRegistry { + + /** + * Returns a handler for the alias or null if not found. + */ + @Nullable + AuthorizationHandler getHandler(String alias); + + /** + * Registers a handler for the given alias. + */ + void register(String alias, AuthorizationHandler handler); + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java new file mode 100644 index 000000000..baafbf4b6 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration; + +import static java.util.Objects.requireNonNull; + +/** + * A configuration that exposes a proxied endpoint via an alias. Each configuration is associated with an extensible {@code authorizationType} such as + * {@link #TOKEN_AUTHORIZATION} (the default) and {@link #NO_AUTHORIZATION}. The {@code proxiedPath} will be prepended to a request sub-path to create an absolute endpoint + * URL where data is fetched from. + */ +public class GatewayConfiguration { + public static final String TOKEN_AUTHORIZATION = "token"; + public static final String NO_AUTHORIZATION = "none"; + + private String alias; + private String proxiedPath; + private String authorizationType = TOKEN_AUTHORIZATION; + + public String getAlias() { + return alias; + } + + public String getProxiedPath() { + return proxiedPath; + } + + public String getAuthorizationType() { + return authorizationType; + } + + private GatewayConfiguration() { + } + + public static class Builder { + + private final GatewayConfiguration configuration; + + public static Builder newInstance() { + return new Builder(); + } + + public Builder alias(String alias) { + this.configuration.alias = alias; + return this; + } + + public Builder proxiedPath(String proxiedPath) { + this.configuration.proxiedPath = proxiedPath; + return this; + } + + public Builder authorizationType(String authorizationType) { + this.configuration.authorizationType = authorizationType; + return this; + } + + public GatewayConfiguration build() { + requireNonNull(configuration.alias, "alias"); + requireNonNull(configuration.proxiedPath, "proxiedPath"); + requireNonNull(configuration.authorizationType, "authorizationType"); + return configuration; + } + + private Builder() { + configuration = new GatewayConfiguration(); + } + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfigurationRegistry.java b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfigurationRegistry.java new file mode 100644 index 000000000..d96ddf730 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfigurationRegistry.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration; + +import org.jetbrains.annotations.Nullable; + +/** + * Manages {@link GatewayConfiguration}s. + */ +public interface GatewayConfigurationRegistry { + + /** + * Returns the configuration for the given alias or null if not found. + */ + @Nullable + GatewayConfiguration getConfiguration(String alias); + + /** + * Registers a configuration for the given alias. + */ + void register(GatewayConfiguration configuration); + +} diff --git a/edc-extensions/business-partner-validation/build.gradle.kts b/edc-extensions/business-partner-validation/build.gradle.kts index 87311b589..b4996ba37 100644 --- a/edc-extensions/business-partner-validation/build.gradle.kts +++ b/edc-extensions/business-partner-validation/build.gradle.kts @@ -23,8 +23,8 @@ plugins { } dependencies { - api(edc.spi.core) - implementation(edc.spi.policy) - implementation(edc.spi.contract) - implementation(edc.spi.policyengine) + api(libs.edc.spi.core) + implementation(libs.edc.spi.policy) + implementation(libs.edc.spi.contract) + implementation(libs.edc.spi.policyengine) } diff --git a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java index d88293a72..1786897bd 100644 --- a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java +++ b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java @@ -35,6 +35,7 @@ import org.eclipse.tractusx.edc.validation.businesspartner.functions.BusinessPartnerProhibitionFunction; import static org.eclipse.edc.policy.engine.spi.PolicyEngine.ALL_SCOPES; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; public class BusinessPartnerValidationExtension implements ServiceExtension { @@ -54,7 +55,8 @@ public class BusinessPartnerValidationExtension implements ServiceExtension { * } * */ - public static final String BUSINESS_PARTNER_CONSTRAINT_KEY = "BusinessPartnerNumber"; + // TODO replace with TX namespace + public static final String BUSINESS_PARTNER_CONSTRAINT_KEY = EDC_NAMESPACE + "BusinessPartnerNumber"; public static final String DEFAULT_LOG_AGREEMENT_EVALUATION = "true"; diff --git a/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java b/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java index 2bc0738b0..c6ed58e43 100644 --- a/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java +++ b/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java @@ -159,8 +159,8 @@ void testValidationWhenSingleParticipantIsValidWithAgreement() { var agreement = ContractAgreement.Builder.newInstance() .id("agreementId") - .providerAgentId("provider") - .consumerAgentId("consumer") + .providerId("provider") + .consumerId("consumer") .assetId("assetId") .policy(Policy.Builder.newInstance().build()) .build(); diff --git a/edc-extensions/control-plane-adapter-api/build.gradle.kts b/edc-extensions/control-plane-adapter-api/build.gradle.kts new file mode 100644 index 000000000..2541d1346 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` + `maven-publish` + id("io.swagger.core.v3.swagger-gradle-plugin") +} + +dependencies { + implementation(project(":spi:control-plane-adapter-spi")) + implementation(libs.edc.api.management) + implementation(libs.edc.spi.aggregateservices) + implementation(libs.jakarta.rsApi) + + testImplementation(testFixtures(libs.edc.core.jersey)) + testImplementation(libs.restAssured) + testImplementation(libs.edc.junit) +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterApiExtension.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterApiExtension.java new file mode 100644 index 000000000..e67f35579 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterApiExtension.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import org.eclipse.edc.connector.api.management.configuration.ManagementApiConfiguration; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.tractusx.edc.api.cp.adapter.transform.JsonObjectToNegotiateEdrRequestDtoTransformer; +import org.eclipse.tractusx.edc.api.cp.adapter.transform.NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; + +public class AdapterApiExtension implements ServiceExtension { + + @Inject + private WebService webService; + @Inject + private ManagementApiConfiguration apiConfig; + + @Inject + private AdapterTransferProcessService adapterTransferProcessService; + + @Inject + private TypeTransformerRegistry transformerRegistry; + + @Inject + private JsonLd jsonLdService; + + @Override + public void initialize(ServiceExtensionContext context) { + transformerRegistry.register(new NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer()); + transformerRegistry.register(new JsonObjectToNegotiateEdrRequestDtoTransformer()); + webService.registerResource(apiConfig.getContextAlias(), new AdapterEdrController(adapterTransferProcessService, jsonLdService, transformerRegistry)); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApi.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApi.java new file mode 100644 index 000000000..d10d133ba --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApi.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.json.JsonObject; +import org.eclipse.edc.api.model.IdResponseDto; +import org.eclipse.edc.web.spi.ApiErrorDetail; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; + +@OpenAPIDefinition +@Tag(name = "Control Plane Adapter EDR Api") +public interface AdapterEdrApi { + + @Operation(description = "Initiates an EDR negotiation by handling a contract negotiation first and then a transfer process for a given offer and with the given counter part. Please note that successfully invoking this endpoint " + + "only means that the negotiation was initiated.", + responses = { + @ApiResponse(responseCode = "200", description = "The negotiation was successfully initiated.", + content = @Content(schema = @Schema(implementation = IdResponseDto.class))), + @ApiResponse(responseCode = "400", description = "Request body was malformed", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))), + }) + JsonObject initiateEdrNegotiation(@Schema(implementation = NegotiateEdrRequestDto.class) JsonObject dto); +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrController.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrController.java new file mode 100644 index 000000000..bfbbc483f --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrController.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import jakarta.json.JsonObject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.edc.api.model.IdResponseDto; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.web.spi.exception.InvalidRequestException; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; + +import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; + +@Consumes({MediaType.APPLICATION_JSON}) +@Produces({MediaType.APPLICATION_JSON}) +@Path("/adapter/edrs") +public class AdapterEdrController implements AdapterEdrApi { + + private final AdapterTransferProcessService adapterTransferProcessService; + private final TypeTransformerRegistry transformerRegistry; + private final JsonLd jsonLdService; + + public AdapterEdrController(AdapterTransferProcessService adapterTransferProcessService, JsonLd jsonLdService, TypeTransformerRegistry transformerRegistry) { + this.adapterTransferProcessService = adapterTransferProcessService; + this.jsonLdService = jsonLdService; + this.transformerRegistry = transformerRegistry; + } + + @POST + @Override + public JsonObject initiateEdrNegotiation(JsonObject requestObject) { + var edrNegotiationRequest = jsonLdService.expand(requestObject) + .compose(expanded -> transformerRegistry.transform(expanded, NegotiateEdrRequestDto.class)) + .compose(dto -> transformerRegistry.transform(dto, NegotiateEdrRequest.class)) + .orElseThrow(InvalidRequestException::new); + + var contractNegotiation = adapterTransferProcessService.initiateEdrNegotiation(edrNegotiationRequest).orElseThrow(exceptionMapper(NegotiateEdrRequest.class)); + + var responseDto = IdResponseDto.Builder.newInstance() + .id(contractNegotiation.getId()) + .createdAt(contractNegotiation.getCreatedAt()) + .build(); + + return transformerRegistry.transform(responseDto, JsonObject.class) + .compose(jsonLdService::compact) + .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDto.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDto.java new file mode 100644 index 000000000..074c7017e --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDto.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.eclipse.edc.api.model.CallbackAddressDto; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; + +import java.util.ArrayList; +import java.util.List; + +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public class NegotiateEdrRequestDto { + + public static final String TYPE = EDC_NAMESPACE + "NegotiateEdrRequestDto"; + public static final String CONNECTOR_ADDRESS = EDC_NAMESPACE + "connectorAddress"; + public static final String PROTOCOL = EDC_NAMESPACE + "protocol"; + public static final String CONNECTOR_ID = EDC_NAMESPACE + "connectorId"; + public static final String PROVIDER_ID = EDC_NAMESPACE + "providerId"; + public static final String OFFER = EDC_NAMESPACE + "offer"; + public static final String CALLBACK_ADDRESSES = EDC_NAMESPACE + "callbackAddresses"; + + @NotBlank(message = "connectorAddress is mandatory") + private String connectorAddress; + @NotBlank(message = "protocol is mandatory") + private String protocol = "ids-multipart"; + @NotBlank(message = "connectorId is mandatory") + private String connectorId; + + private String providerId; + + @NotNull(message = "offer cannot be null") + private ContractOfferDescription offer; + private List callbackAddresses = new ArrayList<>(); + + private NegotiateEdrRequestDto() { + + } + + public String getConnectorAddress() { + return connectorAddress; + } + + public String getProtocol() { + return protocol; + } + + public String getConnectorId() { + return connectorId; + } + + public String getProviderId() { + return providerId; + } + + public List getCallbackAddresses() { + return callbackAddresses; + } + + public ContractOfferDescription getOffer() { + return offer; + } + + public static final class Builder { + private final NegotiateEdrRequestDto dto; + + private Builder() { + dto = new NegotiateEdrRequestDto(); + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder connectorAddress(String connectorAddress) { + dto.connectorAddress = connectorAddress; + return this; + } + + public Builder protocol(String protocol) { + dto.protocol = protocol; + return this; + } + + public Builder connectorId(String connectorId) { + dto.connectorId = connectorId; + return this; + } + + public Builder offer(ContractOfferDescription offer) { + dto.offer = offer; + return this; + } + + public Builder providerId(String providerId) { + dto.providerId = providerId; + return this; + } + + public Builder callbackAddresses(List callbackAddresses) { + dto.callbackAddresses = callbackAddresses; + return this; + } + + public NegotiateEdrRequestDto build() { + return dto; + } + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformer.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformer.java new file mode 100644 index 000000000..02a53ab18 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformer.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.transform; + +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.edc.api.model.CallbackAddressDto; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; +import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; + +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.CALLBACK_ADDRESSES; +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.CONNECTOR_ADDRESS; +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.CONNECTOR_ID; +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.OFFER; +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.PROTOCOL; +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.PROVIDER_ID; + + +public class JsonObjectToNegotiateEdrRequestDtoTransformer extends AbstractJsonLdTransformer { + + public JsonObjectToNegotiateEdrRequestDtoTransformer() { + super(JsonObject.class, NegotiateEdrRequestDto.class); + } + + @Override + public @Nullable NegotiateEdrRequestDto transform(@NotNull JsonObject jsonObject, @NotNull TransformerContext context) { + var builder = NegotiateEdrRequestDto.Builder.newInstance(); + + visitProperties(jsonObject, (k, v) -> setProperties(k, v, builder, context)); + return builder.build(); + } + + private void setProperties(String key, JsonValue value, NegotiateEdrRequestDto.Builder builder, TransformerContext context) { + switch (key) { + case CONNECTOR_ADDRESS: + transformString(value, builder::connectorAddress, context); + break; + case PROTOCOL: + transformString(value, builder::protocol, context); + break; + case CONNECTOR_ID: + transformString(value, builder::connectorId, context); + break; + case PROVIDER_ID: + transformString(value, builder::providerId, context); + break; + case CALLBACK_ADDRESSES: + var addresses = new ArrayList(); + transformArrayOrObject(value, CallbackAddressDto.class, addresses::add, context); + builder.callbackAddresses(addresses); + break; + case OFFER: + transformArrayOrObject(value, ContractOfferDescription.class, builder::offer, context); + break; + default: + context.problem() + .unexpectedType() + .type(NegotiateEdrRequestDto.TYPE) + .property(key) + .actual(key) + .expected(CONNECTOR_ADDRESS) + .expected(PROTOCOL) + .expected(CONNECTOR_ID) + .expected(PROVIDER_ID) + .expected(CALLBACK_ADDRESSES) + .expected(OFFER) + .report(); + break; + } + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer.java new file mode 100644 index 000000000..25878c0ed --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.transform; + +import org.eclipse.edc.api.transformer.DtoTransformer; +import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Collectors; + +public class NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer implements DtoTransformer { + + @Override + public Class getInputType() { + return NegotiateEdrRequestDto.class; + } + + @Override + public Class getOutputType() { + return NegotiateEdrRequest.class; + } + + @Override + public @Nullable NegotiateEdrRequest transform(@NotNull NegotiateEdrRequestDto object, @NotNull TransformerContext context) { + var callbacks = object.getCallbackAddresses().stream().map(c -> context.transform(c, CallbackAddress.class)).collect(Collectors.toList()); + + var contractOffer = ContractOffer.Builder.newInstance() + .id(object.getOffer().getOfferId()) + .assetId(object.getOffer().getAssetId()) + .providerId(getId(object.getProviderId(), object.getConnectorAddress())) + .policy(object.getOffer().getPolicy()) + .build(); + + return NegotiateEdrRequest.Builder.newInstance() + .connectorId(object.getConnectorId()) + .connectorAddress(object.getConnectorAddress()) + .protocol(object.getProtocol()) + .offer(contractOffer) + .callbackAddresses(callbacks) + .build(); + } + + private String getId(String value, String defaultValue) { + return value != null ? value : defaultValue; + } + +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/control-plane-adapter-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..23ba7b21c --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + +org.eclipse.tractusx.edc.api.cp.adapter.AdapterApiExtension diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApiExtensionTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApiExtensionTest.java new file mode 100644 index 000000000..fbe6c1744 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApiExtensionTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import org.eclipse.edc.connector.api.management.configuration.ManagementApiConfiguration; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.injection.ObjectFactory; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.tractusx.edc.api.cp.adapter.transform.JsonObjectToNegotiateEdrRequestDtoTransformer; +import org.eclipse.tractusx.edc.api.cp.adapter.transform.NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +public class AdapterEdrApiExtensionTest { + + AdapterApiExtension extension; + + TypeTransformerRegistry transformerRegistry = mock(TypeTransformerRegistry.class); + + WebService webService = mock(WebService.class); + + ManagementApiConfiguration configuration = mock(ManagementApiConfiguration.class); + + @BeforeEach + void setUp(ObjectFactory factory, ServiceExtensionContext context) { + context.registerService(WebService.class, webService); + context.registerService(TypeTransformerRegistry.class, transformerRegistry); + context.registerService(ManagementApiConfiguration.class, configuration); + extension = factory.constructInstance(AdapterApiExtension.class); + } + + @Test + void initialize_ShouldConfigureTheController(ServiceExtensionContext context) { + var alias = "context"; + + when(configuration.getContextAlias()).thenReturn(alias); + extension.initialize(context); + + verify(webService).registerResource(eq(alias), isA(AdapterEdrController.class)); + verify(transformerRegistry).register(isA(NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer.class)); + verify(transformerRegistry).register(isA(JsonObjectToNegotiateEdrRequestDtoTransformer.class)); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java new file mode 100644 index 000000000..022aa60df --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import io.restassured.specification.RequestSpecification; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.edc.api.model.IdResponseDto; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.junit.annotations.ApiTest; +import org.eclipse.edc.service.spi.result.ServiceResult; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.eclipse.edc.api.model.IdResponseDto.EDC_ID_RESPONSE_DTO_TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.tractusx.edc.api.cp.adapter.TestFunctions.openRequest; +import static org.eclipse.tractusx.edc.api.cp.adapter.TestFunctions.requestDto; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ApiTest +public class AdapterEdrControllerTest extends RestControllerTestBase { + + public static final String ADAPTER_EDR_PATH = "/adapter/edrs"; + private final JsonLd jsonLdService = new TitaniumJsonLd(monitor); + AdapterTransferProcessService adapterTransferProcessService = mock(AdapterTransferProcessService.class); + TypeTransformerRegistry transformerRegistry = mock(TypeTransformerRegistry.class); + + @Test + void initEdrNegotiation_shouldWork_whenValidRequest() { + + var openRequest = openRequest(); + var contractNegotiation = getContractNegotiation(); + var responseBody = Json.createObjectBuilder().add(TYPE, EDC_ID_RESPONSE_DTO_TYPE).add(ID, contractNegotiation.getId()).build(); + + when(transformerRegistry.transform(any(JsonObject.class), eq(NegotiateEdrRequestDto.class))).thenReturn(Result.success(NegotiateEdrRequestDto.Builder.newInstance().build())); + when(transformerRegistry.transform(any(), eq(NegotiateEdrRequest.class))).thenReturn(Result.success(openRequest)); + when(adapterTransferProcessService.initiateEdrNegotiation(openRequest)).thenReturn(ServiceResult.success(contractNegotiation)); + when(transformerRegistry.transform(any(IdResponseDto.class), eq(JsonObject.class))).thenReturn(Result.success(responseBody)); + var request = requestDto(); + + baseRequest() + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .post(ADAPTER_EDR_PATH) + .then() + .statusCode(200) + .body(ID, is(contractNegotiation.getId())); + + } + + @Test + void initEdrNegotiation_shouldReturnBadRequest_whenValidInvalidRequest() { + + var request = NegotiateEdrRequestDto.Builder.newInstance().build(); + when(transformerRegistry.transform(any(JsonObject.class), eq(NegotiateEdrRequestDto.class))).thenReturn(Result.failure("fail")); + + baseRequest() + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .post(ADAPTER_EDR_PATH) + .then() + .statusCode(400); + + } + + @Override + protected Object controller() { + return new AdapterEdrController(adapterTransferProcessService, jsonLdService, transformerRegistry); + } + + private RequestSpecification baseRequest() { + return given() + .baseUri("http://localhost:" + port) + .basePath("/") + .when(); + } + + private ContractNegotiation getContractNegotiation() { + return ContractNegotiation.Builder.newInstance() + .id("id") + .counterPartyAddress("http://test") + .counterPartyId("provider") + .protocol("protocol") + .build(); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java new file mode 100644 index 000000000..ec1a89824 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; +import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; + +import java.util.UUID; + +public class TestFunctions { + + public static ContractOfferDescription createOffer(String offerId, String assetId) { + return ContractOfferDescription.Builder.newInstance() + .offerId(offerId) + .assetId(assetId) + .policy(Policy.Builder.newInstance().build()) + .build(); + } + + public static ContractOfferDescription createOffer(Policy policy) { + return ContractOfferDescription.Builder.newInstance() + .offerId(UUID.randomUUID().toString()) + .assetId(UUID.randomUUID().toString()) + .policy(policy) + .build(); + } + + public static ContractOfferDescription createOffer(String offerId) { + return createOffer(offerId, UUID.randomUUID().toString()); + } + + public static ContractOfferDescription createOffer() { + return createOffer(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + } + + public static NegotiateEdrRequestDto requestDto() { + return NegotiateEdrRequestDto.Builder.newInstance() + .connectorAddress("test") + .connectorId("id") + .protocol("test-protocol") + .offer(ContractOfferDescription.Builder.newInstance() + .offerId("offerId") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()).build()) + .build(); + } + + public static NegotiateEdrRequest openRequest() { + return NegotiateEdrRequest.Builder.newInstance() + .connectorAddress("test") + .connectorId("id") + .protocol("test-protocol") + .offer(ContractOffer.Builder.newInstance() + .id("offerId") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()).build()) + .build(); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDtoValidationTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDtoValidationTest.java new file mode 100644 index 000000000..88ee7633a --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDtoValidationTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.dto; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NegotiateEdrRequestDtoValidationTest { + + private Validator validator; + + @BeforeEach + void setUp() { + try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) { + validator = factory.getValidator(); + } + } + + @Test + void validate_invalidDto() { + var dto = NegotiateEdrRequestDto.Builder.newInstance().build(); + assertThat(validator.validate(dto)).hasSize(3); + } + +} diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformerTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformerTest.java new file mode 100644 index 000000000..5c52ad494 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformerTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.transform; + +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.edc.api.model.CallbackAddressDto; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.edc.transform.spi.ProblemBuilder; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.ASSET_ID; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.CALLBACK_ADDRESSES; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.CONNECTOR_ADDRESS; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.CONNECTOR_ID; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.OFFER; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.OFFER_ID; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.POLICY; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.PROTOCOL; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.PROVIDER_ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OBLIGATION_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PERMISSION_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_POLICY_TYPE_SET; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PROHIBITION_ATTRIBUTE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.EVENTS; +import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.IS_TRANSACTIONAL; +import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.URI; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class JsonObjectToNegotiateEdrRequestDtoTransformerTest { + + private final JsonLd jsonLd = new TitaniumJsonLd(mock(Monitor.class)); + private final TransformerContext context = mock(TransformerContext.class); + private JsonObjectToNegotiateEdrRequestDtoTransformer transformer; + + @BeforeEach + void setUp() { + transformer = new JsonObjectToNegotiateEdrRequestDtoTransformer(); + } + + @Test + void transform() { + var jsonObject = Json.createObjectBuilder() + .add(TYPE, NegotiationInitiateRequestDto.TYPE) + .add(CONNECTOR_ADDRESS, "test-address") + .add(PROTOCOL, "test-protocol") + .add(CONNECTOR_ID, "test-conn-id") + .add(PROVIDER_ID, "test-provider-id") + .add(CALLBACK_ADDRESSES, createCallbackAddress()) + .add(OFFER, Json.createObjectBuilder() + .add(OFFER_ID, "test-offer-id") + .add(ASSET_ID, "test-asset") + .add(POLICY, createPolicy()) + .build()) + .build(); + + when(context.transform(any(JsonValue.class), eq(ContractOfferDescription.class))).thenReturn(ContractOfferDescription.Builder.newInstance().build()); + + when(context.transform(any(JsonObject.class), eq(CallbackAddress.class))).thenReturn(CallbackAddress.Builder.newInstance() + .uri("http://test.local") + .events(Set.of("foo", "bar")) + .transactional(true) + .build()); + when(context.transform(any(CallbackAddress.class), eq(CallbackAddressDto.class))).thenReturn(CallbackAddressDto.Builder.newInstance() + .uri("http://test.local") + .events(Set.of("foo", "bar")) + .transactional(true) + .build()); + var dto = transformer.transform(jsonLd.expand(jsonObject).getContent(), context); + + assertThat(dto).isNotNull(); + assertThat(dto.getCallbackAddresses()).isNotEmpty(); + assertThat(dto.getProtocol()).isEqualTo("test-protocol"); + assertThat(dto.getConnectorAddress()).isEqualTo("test-address"); + assertThat(dto.getConnectorId()).isEqualTo("test-conn-id"); + assertThat(dto.getProviderId()).isEqualTo("test-provider-id"); + assertThat(dto.getOffer()).isNotNull(); + + } + + @Test + void transform_reportErrors() { + + when(context.problem()).thenReturn(new ProblemBuilder(context)); + + var jsonObject = Json.createObjectBuilder() + .add(TYPE, NegotiationInitiateRequestDto.TYPE) + .add(EDC_NAMESPACE + "notFound", "test-address") + .build(); + + var dto = transformer.transform(jsonLd.expand(jsonObject).getContent(), context); + + assertThat(dto).isNotNull(); + verify(context, times(1)).reportProblem(anyString()); + } + + private JsonArrayBuilder createCallbackAddress() { + var builder = Json.createArrayBuilder(); + return builder.add(Json.createObjectBuilder() + .add(IS_TRANSACTIONAL, true) + .add(URI, "http://test.local/") + .add(EVENTS, Json.createArrayBuilder().build())); + } + + private JsonObject createPolicy() { + var permissionJson = getJsonObject("permission"); + var prohibitionJson = getJsonObject("prohibition"); + var dutyJson = getJsonObject("duty"); + return Json.createObjectBuilder() + .add(TYPE, ODRL_POLICY_TYPE_SET) + .add(ODRL_PERMISSION_ATTRIBUTE, permissionJson) + .add(ODRL_PROHIBITION_ATTRIBUTE, prohibitionJson) + .add(ODRL_OBLIGATION_ATTRIBUTE, dutyJson) + .build(); + } + + private JsonObject getJsonObject(String type) { + return Json.createObjectBuilder() + .add(TYPE, type) + .build(); + } +} \ No newline at end of file diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiateEdrRequestTransformerTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiateEdrRequestTransformerTest.java new file mode 100644 index 000000000..f51ba1435 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiateEdrRequestTransformerTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.transform; + +import org.eclipse.edc.api.model.CallbackAddressDto; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.api.cp.adapter.TestFunctions.createOffer; +import static org.mockito.Mockito.mock; + +public class NegotiateEdrRequestDtoToNegotiateEdrRequestTransformerTest { + + private final NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer transformer = new NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer(); + + private final TransformerContext context = mock(TransformerContext.class); + + @Test + void inputOutputType() { + assertThat(transformer.getInputType()).isNotNull(); + assertThat(transformer.getOutputType()).isNotNull(); + } + + @Test + void verify_transform() { + var callback = CallbackAddressDto.Builder.newInstance() + .uri("local://test") + .build(); + var dto = NegotiateEdrRequestDto.Builder.newInstance() + .connectorId("connectorId") + .connectorAddress("address") + .protocol("protocol") + .providerId("test-provider") + .offer(createOffer("offerId", "assetId")) + .callbackAddresses(List.of(callback)) + .build(); + + var request = transformer.transform(dto, context); + + assertThat(request).isNotNull(); + assertThat(request.getConnectorId()).isEqualTo("connectorId"); + assertThat(request.getConnectorAddress()).isEqualTo("address"); + assertThat(request.getProtocol()).isEqualTo("protocol"); + assertThat(request.getOffer().getId()).isEqualTo("offerId"); + assertThat(request.getOffer().getPolicy()).isNotNull(); + assertThat(request.getCallbackAddresses()).hasSize(1); + } + + @Test + void verify_transfor_withNoProviderId() { + var dto = NegotiateEdrRequestDto.Builder.newInstance() + .connectorId("connectorId") + .connectorAddress("address") + .protocol("protocol") + // do not set provider ID + .offer(createOffer("offerId", "assetId")) + .build(); + + var request = transformer.transform(dto, context); + + assertThat(request).isNotNull(); + assertThat(request.getOffer().getProviderId()).asString().isEqualTo(dto.getConnectorAddress()); + } + + @Test + void verify_transform_withNoConsumerId() { + var dto = NegotiateEdrRequestDto.Builder.newInstance() + .connectorId("connectorId") + .connectorAddress("address") + .protocol("protocol") + // do not set consumer ID + .providerId("urn:connector:test-provider") + .offer(createOffer("offerId", "assetId")) + .build(); + + var request = transformer.transform(dto, context); + assertThat(request).isNotNull(); + assertThat(request.getOffer().getProviderId()).asString().isEqualTo("urn:connector:test-provider"); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/build.gradle.kts b/edc-extensions/control-plane-adapter-callback/build.gradle.kts new file mode 100644 index 000000000..ffb126fed --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` + `maven-publish` +} + +dependencies { + implementation(project(":spi:control-plane-adapter-spi")) + implementation(project(":spi:edr-cache-spi")) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.transfer) + implementation(libs.edc.spi.contract) + implementation(libs.edc.spi.controlplane) + implementation(libs.edc.spi.aggregateservices) + + testImplementation(libs.edc.junit) +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImpl.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImpl.java new file mode 100644 index 000000000..0220b1330 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImpl.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequestData; +import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.service.spi.result.ServiceResult; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; + +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class AdapterTransferProcessServiceImpl implements AdapterTransferProcessService { + + public static final String LOCAL_ADAPTER_URI = "local://adapter"; + public static final Set LOCAL_EVENTS = Set.of("contract.negotiation", "transfer.process"); + public static final CallbackAddress LOCAL_CALLBACK = CallbackAddress.Builder.newInstance() + .transactional(true) + .uri(LOCAL_ADAPTER_URI) + .events(LOCAL_EVENTS) + .build(); + private final ContractNegotiationService contractNegotiationService; + + public AdapterTransferProcessServiceImpl(ContractNegotiationService contractNegotiationService) { + this.contractNegotiationService = contractNegotiationService; + } + + @Override + public ServiceResult initiateEdrNegotiation(NegotiateEdrRequest request) { + var contractNegotiation = contractNegotiationService.initiateNegotiation(createContractRequest(request)); + return ServiceResult.success(contractNegotiation); + } + + private ContractRequest createContractRequest(NegotiateEdrRequest request) { + var callbacks = Stream.concat(request.getCallbackAddresses().stream(), Stream.of(LOCAL_CALLBACK)).collect(Collectors.toList()); + + var requestData = ContractRequestData.Builder.newInstance() + .contractOffer(request.getOffer()) + .protocol(request.getProtocol()) + .counterPartyAddress(request.getConnectorAddress()) + .connectorId(request.getConnectorId()) + .build(); + + return ContractRequest.Builder.newInstance() + .requestData(requestData) + .callbackAddresses(callbacks).build(); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallback.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallback.java new file mode 100644 index 000000000..80082a366 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallback.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFinalized; +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; +import org.eclipse.edc.connector.transfer.spi.types.DataRequest; +import org.eclipse.edc.connector.transfer.spi.types.TransferRequest; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallback; + +import java.util.UUID; + +import static java.lang.String.format; + +public class ContractNegotiationCallback implements InProcessCallback { + + public static final DataAddress DATA_DESTINATION = DataAddress.Builder.newInstance().type("HttpProxy").build(); + private final TransferProcessService transferProcessService; + + private final Monitor monitor; + + public ContractNegotiationCallback(TransferProcessService transferProcessService, Monitor monitor) { + this.transferProcessService = transferProcessService; + this.monitor = monitor; + } + + @Override + public Result invoke(CallbackEventRemoteMessage message) { + if (message.getEventEnvelope().getPayload() instanceof ContractNegotiationFinalized) { + return initiateTransfer((ContractNegotiationFinalized) message.getEventEnvelope().getPayload()); + } + return Result.success(); + } + + private Result initiateTransfer(ContractNegotiationFinalized negotiationFinalized) { + + var dataRequest = + DataRequest.Builder.newInstance() + .id(UUID.randomUUID().toString()) + .assetId(negotiationFinalized.getContractAgreement().getAssetId()) + .contractId(negotiationFinalized.getContractAgreement().getId()) + .connectorId(negotiationFinalized.getCounterPartyId()) + .connectorAddress(negotiationFinalized.getCounterPartyAddress()) + .protocol(negotiationFinalized.getProtocol()) + .dataDestination(DATA_DESTINATION) + .managedResources(false) + .build(); + + var transferRequest = TransferRequest.Builder.newInstance() + .dataRequest(dataRequest) + .callbackAddresses(negotiationFinalized.getCallbackAddresses()) + .build(); + + var result = transferProcessService.initiateTransfer(transferRequest); + + if (result.failed()) { + var msg = format("Failed to initiate a transfer for contract %s and asset %s, error: %s", negotiationFinalized.getContractAgreement().getId(), negotiationFinalized.getContractAgreement().getAssetId(), result.getFailureDetail()); + monitor.severe(msg); + return Result.failure(msg); + } + monitor.debug(format("Transfer with id %s initiated", result.getContent())); + return Result.success(); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcher.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcher.java new file mode 100644 index 000000000..4b0130b24 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcher.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.message.RemoteMessageDispatcher; +import org.eclipse.edc.spi.types.domain.message.RemoteMessage; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallbackRegistry; + +import java.util.concurrent.CompletableFuture; + +import static java.lang.String.format; + +public class InProcessCallbackMessageDispatcher implements RemoteMessageDispatcher { + + public static final String CALLBACK_EVENT_LOCAL = "callback-event-local"; + + private final InProcessCallbackRegistry registry; + + public InProcessCallbackMessageDispatcher(InProcessCallbackRegistry registry) { + this.registry = registry; + } + + @Override + public String protocol() { + return CALLBACK_EVENT_LOCAL; + } + + @Override + public CompletableFuture send(Class responseType, M message) { + if (message instanceof CallbackEventRemoteMessage) { + var result = registry.handleMessage((CallbackEventRemoteMessage) message); + if (result.succeeded()) { + return CompletableFuture.completedFuture(null); + } else { + return CompletableFuture.failedFuture(new EdcException(result.getFailureDetail())); + } + } + return CompletableFuture.failedFuture(new EdcException(format("Message of type %s not supported", message.getClass().getSimpleName()))); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtension.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtension.java new file mode 100644 index 000000000..f7bb6ba06 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtension.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallbackRegistry; + +@Extension(InProcessCallbackRegistryExtension.NAME) +public class InProcessCallbackRegistryExtension implements ServiceExtension { + + public static final String NAME = "In process callback registry extension"; + + @Override + public String name() { + return NAME; + } + + @Provider + public InProcessCallbackRegistry callbackRegistry() { + return new InProcessCallbackRegistryImpl(); + } + +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryImpl.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryImpl.java new file mode 100644 index 000000000..d65c4e5ae --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryImpl.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallback; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallbackRegistry; + +import java.util.ArrayList; +import java.util.List; + +public class InProcessCallbackRegistryImpl implements InProcessCallbackRegistry { + + private final List handlers = new ArrayList<>(); + + @Override + public void registerHandler(InProcessCallback callback) { + handlers.add(callback); + } + + @Override + public Result handleMessage(CallbackEventRemoteMessage message) { + return handlers.stream() + .map(handler -> handler.invoke(message)) + .filter(Result::failed) + .findFirst() + .orElseGet(Result::success); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtension.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtension.java new file mode 100644 index 000000000..4fe11df5d --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtension.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackProtocolResolverRegistry; +import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; +import org.eclipse.edc.connector.transfer.spi.store.TransferProcessStore; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.transaction.spi.TransactionContext; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallbackRegistry; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; + +import static org.eclipse.tractusx.edc.cp.adapter.callback.InProcessCallbackMessageDispatcher.CALLBACK_EVENT_LOCAL; + +@Provides(AdapterTransferProcessService.class) +@Extension(LocalCallbackExtension.NAME) +public class LocalCallbackExtension implements ServiceExtension { + public static final String NAME = "Local callbacks extension"; + + public static final String LOCAL = "local"; + @Inject + private RemoteMessageDispatcherRegistry registry; + + @Inject + private CallbackProtocolResolverRegistry resolverRegistry; + + @Inject + private TransferProcessService transferProcessService; + + @Inject + private ContractNegotiationService contractNegotiationService; + + @Inject + private TransferProcessStore transferProcessStore; + + @Inject + private EndpointDataReferenceCache edrCache; + + @Inject + private InProcessCallbackRegistry callbackRegistry; + + @Inject + private Monitor monitor; + + @Inject + private TransactionContext transactionContext; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + + callbackRegistry.registerHandler(new ContractNegotiationCallback(transferProcessService, monitor)); + callbackRegistry.registerHandler(new TransferProcessLocalCallback(edrCache, transferProcessStore, transactionContext)); + + resolverRegistry.registerResolver(this::resolveProtocol); + registry.register(new InProcessCallbackMessageDispatcher(callbackRegistry)); + + context.registerService(AdapterTransferProcessService.class, new AdapterTransferProcessServiceImpl(contractNegotiationService)); + } + + private String resolveProtocol(String scheme) { + + if (scheme.equalsIgnoreCase(LOCAL)) { + return CALLBACK_EVENT_LOCAL; + } + return null; + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallback.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallback.java new file mode 100644 index 000000000..6d450bfd8 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallback.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessStarted; +import org.eclipse.edc.connector.transfer.spi.store.TransferProcessStore; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataAddressConstants; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.edc.transaction.spi.TransactionContext; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallback; + +import static java.lang.String.format; + +public class TransferProcessLocalCallback implements InProcessCallback { + + private final EndpointDataReferenceCache edrCache; + private final TransferProcessStore transferProcessStore; + + private final TransactionContext transactionContext; + + public TransferProcessLocalCallback(EndpointDataReferenceCache edrCache, TransferProcessStore transferProcessStore, TransactionContext transactionContext) { + this.edrCache = edrCache; + this.transferProcessStore = transferProcessStore; + this.transactionContext = transactionContext; + } + + @Override + public Result invoke(CallbackEventRemoteMessage message) { + if (message.getEventEnvelope().getPayload() instanceof TransferProcessStarted) { + var transferProcessStarted = (TransferProcessStarted) message.getEventEnvelope().getPayload(); + if (transferProcessStarted.getDataAddress() != null) { + return EndpointDataAddressConstants.to(transferProcessStarted.getDataAddress()) + .compose(this::storeEdr) + .mapTo(); + } + } + return Result.success(); + } + + private Result storeEdr(EndpointDataReference edr) { + return transactionContext.execute(() -> { + // TODO upstream api for getting the TP with the DataRequest#id + var transferProcessId = transferProcessStore.processIdForDataRequestId(edr.getId()); + var transferProcess = transferProcessStore.findById(transferProcessId); + if (transferProcess != null) { + var cacheEntry = EndpointDataReferenceEntry.Builder.newInstance(). + transferProcessId(transferProcess.getId()) + .assetId(transferProcess.getDataRequest().getAssetId()) + .agreementId(transferProcess.getDataRequest().getContractId()) + .build(); + + edrCache.save(cacheEntry, edr); + return Result.success(); + } else { + return Result.failure(format("Failed to find a transfer process with ID %s", transferProcessId)); + } + }); + + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/control-plane-adapter-callback/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..210521e83 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,16 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + +org.eclipse.tractusx.edc.cp.adapter.callback.InProcessCallbackRegistryExtension +org.eclipse.tractusx.edc.cp.adapter.callback.LocalCallbackExtension diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImplTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImplTest.java new file mode 100644 index 000000000..1e95f0e1c --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImplTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest; +import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; +import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.cp.adapter.callback.AdapterTransferProcessServiceImpl.LOCAL_CALLBACK; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AdapterTransferProcessServiceImplTest { + + ContractNegotiationService contractNegotiationService = mock(ContractNegotiationService.class); + + @Test + void initEdrNegotiation_shouldFireAContractNegotiation_WhenUsingCallbacks() { + var transferService = new AdapterTransferProcessServiceImpl(contractNegotiationService); + + var captor = ArgumentCaptor.forClass(ContractRequest.class); + + when(contractNegotiationService.initiateNegotiation(any())).thenReturn(getContractNegotiation()); + + var negotiateEdrRequest = getNegotiateEdrRequest(); + + var result = transferService.initiateEdrNegotiation(negotiateEdrRequest); + + assertThat(result.succeeded()).isTrue(); + assertThat(result.getContent()).isNotNull(); + + verify(contractNegotiationService).initiateNegotiation(captor.capture()); + + var msg = captor.getValue(); + + assertThat(msg.getCallbackAddresses()).usingRecursiveFieldByFieldElementComparator().containsAll(negotiateEdrRequest.getCallbackAddresses()); + assertThat(msg.getCallbackAddresses()).usingRecursiveFieldByFieldElementComparator().contains(LOCAL_CALLBACK); + assertThat(msg.getRequestData().getContractOffer()).usingRecursiveComparison().isEqualTo(negotiateEdrRequest.getOffer()); + assertThat(msg.getRequestData().getProtocol()).isEqualTo(negotiateEdrRequest.getProtocol()); + assertThat(msg.getRequestData().getCounterPartyAddress()).isEqualTo(negotiateEdrRequest.getConnectorAddress()); + + } + + private NegotiateEdrRequest getNegotiateEdrRequest() { + return NegotiateEdrRequest.Builder.newInstance() + .protocol("protocol") + .connectorAddress("http://test") + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance().uri("test").events(Set.of("test")).build())) + .offer(ContractOffer.Builder.newInstance() + .id("id") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()) + .providerId("provider") + .build()) + .build(); + } + + private ContractNegotiation getContractNegotiation() { + return ContractNegotiation.Builder.newInstance() + .id("id") + .counterPartyAddress("http://test") + .counterPartyId("provider") + .protocol("protocol") + .build(); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallbackTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallbackTest.java new file mode 100644 index 000000000..d24f05382 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallbackTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationAccepted; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationConfirmed; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationDeclined; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationEvent; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFailed; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationInitiated; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationOffered; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationRequested; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationTerminated; +import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; +import org.eclipse.edc.connector.transfer.spi.types.TransferProcess; +import org.eclipse.edc.connector.transfer.spi.types.TransferRequest; +import org.eclipse.edc.service.spi.result.ServiceResult; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.cp.adapter.callback.ContractNegotiationCallback.DATA_DESTINATION; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.getNegotiationFinalizedEvent; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.remoteMessage; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + + +public class ContractNegotiationCallbackTest { + + TransferProcessService transferProcessService = mock(TransferProcessService.class); + + Monitor monitor = mock(Monitor.class); + + ContractNegotiationCallback callback; + + @BeforeEach + void setup() { + callback = new ContractNegotiationCallback(transferProcessService, monitor); + } + + @Test + void invoke_shouldStartATransferProcess() { + + var captor = ArgumentCaptor.forClass(TransferRequest.class); + + when(transferProcessService.initiateTransfer(any())).thenReturn(ServiceResult.success(TransferProcess.Builder.newInstance().id("test").build())); + + var event = getNegotiationFinalizedEvent(); + var message = remoteMessage(event); + + var result = callback.invoke(message); + + assertThat(result.succeeded()).isTrue(); + verify(transferProcessService).initiateTransfer(captor.capture()); + + + var tp = captor.getValue(); + + assertThat(tp.getCallbackAddresses()).usingRecursiveFieldByFieldElementComparator().containsAll(event.getCallbackAddresses()); + + assertThat(tp.getDataRequest()).satisfies(dataRequest -> { + + assertThat(dataRequest.getContractId()).isEqualTo(event.getContractAgreement().getId()); + assertThat(dataRequest.getAssetId()).isEqualTo(event.getContractAgreement().getAssetId()); + assertThat(dataRequest.getConnectorAddress()).isEqualTo(event.getCounterPartyAddress()); + assertThat(dataRequest.getConnectorId()).isEqualTo(event.getCounterPartyId()); + assertThat(dataRequest.getProtocol()).isEqualTo(event.getProtocol()); + assertThat(dataRequest.getDataDestination()).usingRecursiveComparison().isEqualTo(DATA_DESTINATION); + }); + + } + + @Test + void invoke_shouldThrowException_whenATransferRequestFails() { + + when(transferProcessService.initiateTransfer(any())).thenReturn(ServiceResult.badRequest("test")); + + var event = getNegotiationFinalizedEvent(); + var message = remoteMessage(event); + + + var result = callback.invoke(message); + + assertThat(result.failed()).isTrue(); + + } + + @ParameterizedTest + @ArgumentsSource(EventInstances.class) + void invoke_shouldIgnoreOtherEvents(ContractNegotiationEvent event) { + var message = remoteMessage(event); + callback.invoke(message); + + verifyNoInteractions(transferProcessService); + } + + private static class EventInstances implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + baseBuilder(ContractNegotiationAccepted.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationConfirmed.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationDeclined.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationFailed.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationInitiated.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationOffered.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationRequested.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationTerminated.Builder.newInstance()).build() + ).map(Arguments::of); + + } + + private > B baseBuilder(B builder) { + var callbacks = List.of(CallbackAddress.Builder.newInstance().uri("http://local").events(Set.of("test")).build()); + return builder + .contractNegotiationId("id") + .protocol("test") + .callbackAddresses(callbacks) + .counterPartyAddress("addr") + .counterPartyId("provider"); + } + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcherTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcherTest.java new file mode 100644 index 000000000..27ef6565e --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcherTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.message.RemoteMessage; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallback; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.getNegotiationFinalizedEvent; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.remoteMessage; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +public class InProcessCallbackMessageDispatcherTest { + + InProcessCallback callback = mock(InProcessCallback.class); + + InProcessCallbackMessageDispatcher dispatcher; + + @BeforeEach + void setup() { + var registry = new InProcessCallbackRegistryImpl(); + registry.registerHandler(callback); + dispatcher = new InProcessCallbackMessageDispatcher(registry); + } + + @Test + void send_shouldInvokeRegisteredCallback() { + + var msg = remoteMessage(getNegotiationFinalizedEvent()); + when(callback.invoke(any())).thenReturn(Result.success()); + dispatcher.send(Object.class, msg).join(); + + + verify(callback).invoke(msg); + } + + @Test + void send_shouldNotInvokeRegisteredCallback_whenItsNotACallbackRemoteMessage() { + + assertThatThrownBy(() -> dispatcher.send(Object.class, new TestMessage()).join()) + .hasCauseInstanceOf(EdcException.class); + + + verifyNoInteractions(callback); + } + + private static class TestMessage implements RemoteMessage { + + @Override + public String getProtocol() { + return "test"; + } + + @Override + public String getCounterPartyAddress() { + return "test"; + } + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtensionTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtensionTest.java new file mode 100644 index 000000000..9aef1a0b2 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtensionTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.injection.ObjectFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(DependencyInjectionExtension.class) +public class InProcessCallbackRegistryExtensionTest { + + InProcessCallbackRegistryExtension extension; + + + @BeforeEach + void setUp(ObjectFactory factory, ServiceExtensionContext context) { + extension = factory.constructInstance(InProcessCallbackRegistryExtension.class); + } + + @Test + void shouldInitializeTheExtension(ServiceExtensionContext context) { + assertThat(extension.callbackRegistry()).isInstanceOf(InProcessCallbackRegistryImpl.class); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtensionTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtensionTest.java new file mode 100644 index 000000000..8e68c0ed1 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtensionTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackProtocolResolver; +import org.eclipse.edc.connector.spi.callback.CallbackProtocolResolverRegistry; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.injection.ObjectFactory; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallback; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallbackRegistry; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.cp.adapter.callback.InProcessCallbackMessageDispatcher.CALLBACK_EVENT_LOCAL; +import static org.mockito.Mockito.*; + +@ExtendWith(DependencyInjectionExtension.class) +public class LocalCallbackExtensionTest { + + LocalCallbackExtension extension; + + RemoteMessageDispatcherRegistry dispatcherRegistry = mock(RemoteMessageDispatcherRegistry.class); + + CallbackProtocolResolverRegistry resolverRegistry = mock(CallbackProtocolResolverRegistry.class); + + InProcessCallbackRegistry inProcessCallbackRegistry = mock(InProcessCallbackRegistry.class); + + @BeforeEach + void setUp(ObjectFactory factory, ServiceExtensionContext context) { + + context.registerService(RemoteMessageDispatcherRegistry.class, dispatcherRegistry); + context.registerService(CallbackProtocolResolverRegistry.class, resolverRegistry); + context.registerService(InProcessCallbackRegistry.class, inProcessCallbackRegistry); + extension = factory.constructInstance(LocalCallbackExtension.class); + } + + @Test + void shouldInitializeTheExtension(ServiceExtensionContext context) { + extension.initialize(context); + + var captor = ArgumentCaptor.forClass(CallbackProtocolResolver.class); + verify(resolverRegistry).registerResolver(captor.capture()); + + var resolver = captor.getValue(); + assertThat(resolver.resolve("local")).isEqualTo(CALLBACK_EVENT_LOCAL); + assertThat(resolver.resolve("test")).isNull(); + + + var service = context.getService(AdapterTransferProcessService.class); + assertThat(service).isInstanceOf(AdapterTransferProcessServiceImpl.class); + + var callbackArgumentCaptor = ArgumentCaptor.forClass(InProcessCallback.class); + verify(inProcessCallbackRegistry, times(2)).registerHandler(callbackArgumentCaptor.capture()); + + assertThat(callbackArgumentCaptor.getAllValues()) + .flatExtracting(Object::getClass) + .containsExactly(ContractNegotiationCallback.class, TransferProcessLocalCallback.class); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TestFunctions.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TestFunctions.java new file mode 100644 index 000000000..ec8593667 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TestFunctions.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFinalized; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessStarted; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.event.EventEnvelope; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public class TestFunctions { + + public static ContractNegotiationFinalized getNegotiationFinalizedEvent() { + var agreement = ContractAgreement.Builder.newInstance() + .id("id") + .policy(Policy.Builder.newInstance().build()) + .assetId("assetId") + .consumerId("consumer") + .providerId("provider") + .build(); + + return ContractNegotiationFinalized.Builder.newInstance() + .contractNegotiationId("id") + .protocol("test-protocol") + .counterPartyId("counter-party") + .counterPartyAddress("https://counter-party") + .contractAgreement(agreement) + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() + .uri("local://test") + .events(Set.of("test")) + .transactional(true) + .build())) + .build(); + } + + public static TransferProcessStarted getTransferProcessStartedEvent() { + return getTransferProcessStartedEvent(null); + } + + public static TransferProcessStarted getTransferProcessStartedEvent(DataAddress dataAddress) { + return TransferProcessStarted.Builder.newInstance() + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() + .uri("local://test") + .events(Set.of("test")) + .transactional(true) + .build())) + .dataAddress(dataAddress) + .transferProcessId(UUID.randomUUID().toString()) + .build(); + } + + public static EndpointDataReference getEdr() { + return EndpointDataReference.Builder.newInstance() + .id("dataRequestId") + .authCode("authCode") + .authKey("authKey") + .endpoint("http://endpoint") + .build(); + } + + public static CallbackEventRemoteMessage remoteMessage(T event) { + var callback = CallbackAddress.Builder.newInstance() + .events(Set.of("test")) + .uri("local://test") + .build(); + + var envelope = EventEnvelope.Builder + .newInstance() + .id(UUID.randomUUID().toString()) + .at(System.currentTimeMillis()) + .payload(event) + .build(); + return new CallbackEventRemoteMessage(callback, envelope, "local"); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallbackTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallbackTest.java new file mode 100644 index 000000000..b4b6d8480 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallbackTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessCompleted; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessDeprovisioned; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessEvent; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessProvisioned; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessRequested; +import org.eclipse.edc.connector.transfer.spi.store.TransferProcessStore; +import org.eclipse.edc.connector.transfer.spi.types.DataRequest; +import org.eclipse.edc.connector.transfer.spi.types.TransferProcess; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataAddressConstants; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.edc.transaction.spi.NoopTransactionContext; +import org.eclipse.edc.transaction.spi.TransactionContext; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.getEdr; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.getTransferProcessStartedEvent; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.remoteMessage; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + + +public class TransferProcessLocalCallbackTest { + + TransferProcessStore transferProcessStore = mock(TransferProcessStore.class); + EndpointDataReferenceCache edrCache = mock(EndpointDataReferenceCache.class); + + TransactionContext transactionContext = new NoopTransactionContext(); + + TransferProcessLocalCallback callback; + + + @BeforeEach + void setup() { + callback = new TransferProcessLocalCallback(edrCache, transferProcessStore, transactionContext); + } + + @Test + void invoke_shouldStoreTheEdrInCache_whenDataAddressIsPresent() { + + var transferProcessId = "transferProcessId"; + var assetId = "assetId"; + var contractId = "contractId"; + + + var edr = getEdr(); + + when(transferProcessStore.processIdForDataRequestId(edr.getId())).thenReturn(transferProcessId); + + var dataRequest = DataRequest.Builder.newInstance().id(edr.getId()) + .destinationType("HttpProxy") + .assetId(assetId) + .contractId(contractId) + .build(); + + var transferProcess = TransferProcess.Builder.newInstance() + .id(transferProcessId) + .dataRequest(dataRequest) + .build(); + + when(transferProcessStore.findById(transferProcessId)).thenReturn(transferProcess); + + + var event = getTransferProcessStartedEvent(EndpointDataAddressConstants.from(edr)); + + var cacheEntryCaptor = ArgumentCaptor.forClass(EndpointDataReferenceEntry.class); + var edrCaptor = ArgumentCaptor.forClass(EndpointDataReference.class); + var message = remoteMessage(event); + + var result = callback.invoke(message); + assertThat(result.succeeded()).isTrue(); + + verify(edrCache).save(cacheEntryCaptor.capture(), edrCaptor.capture()); + + assertThat(edrCaptor.getValue()).usingRecursiveComparison().isEqualTo(edr); + + } + + @Test + void invoke_shouldNotFail_whenDataAddressIsAbsent() { + + var event = getTransferProcessStartedEvent(); + var message = remoteMessage(event); + + var result = callback.invoke(message); + assertThat(result.succeeded()).isTrue(); + + verifyNoInteractions(edrCache); + verifyNoInteractions(transferProcessStore); + } + + @Test + void invoke_shouldNotFail_whenTransferProcessNotFound() { + + var transferProcessId = "transferProcessId"; + + var edr = getEdr(); + + when(transferProcessStore.processIdForDataRequestId(edr.getId())).thenReturn(transferProcessId); + + when(transferProcessStore.findById(transferProcessId)).thenReturn(null); + + var event = getTransferProcessStartedEvent(EndpointDataAddressConstants.from(edr)); + var message = remoteMessage(event); + + var result = callback.invoke(message); + assertThat(result.succeeded()).isFalse(); + + verifyNoInteractions(edrCache); + } + + @Test + void invoke_shouldFail_withInvalidDataAddress() { + + var event = getTransferProcessStartedEvent(DataAddress.Builder.newInstance().type("HttpProxy").build()); + + var message = remoteMessage(event); + + var result = callback.invoke(message); + assertThat(result.failed()).isTrue(); + + verifyNoInteractions(edrCache); + verifyNoInteractions(transferProcessStore); + } + + @ParameterizedTest + @ArgumentsSource(EventInstances.class) + void invoke_shouldIgnoreOtherEvents(TransferProcessEvent event) { + var message = remoteMessage(event); + var result = callback.invoke(message); + + assertThat(result.succeeded()).isTrue(); + + verifyNoInteractions(edrCache); + } + + private static class EventInstances implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + baseBuilder(TransferProcessRequested.Builder.newInstance()).build(), + baseBuilder(TransferProcessProvisioned.Builder.newInstance()).build(), + baseBuilder(TransferProcessCompleted.Builder.newInstance()).build(), + baseBuilder(TransferProcessDeprovisioned.Builder.newInstance()).build() + ).map(Arguments::of); + + } + + private > B baseBuilder(B builder) { + var callbacks = List.of(CallbackAddress.Builder.newInstance().uri("http://local").events(Set.of("test")).build()); + return builder + .transferProcessId(UUID.randomUUID().toString()) + .callbackAddresses(callbacks); + } + } +} diff --git a/edc-extensions/control-plane-adapter/build.gradle.kts b/edc-extensions/control-plane-adapter/build.gradle.kts index f22dd2e5f..4569de274 100644 --- a/edc-extensions/control-plane-adapter/build.gradle.kts +++ b/edc-extensions/control-plane-adapter/build.gradle.kts @@ -24,22 +24,30 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.spi.policy) - implementation(edc.api.management) - implementation(edc.spi.catalog) - implementation(edc.spi.transactionspi) - implementation(edc.spi.transaction.datasource) - implementation(edc.ids) - implementation(edc.sql.core) - implementation(edc.sql.lease) - implementation(edc.sql.pool) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.policy) + + implementation(libs.edc.api.management) + constraints { + implementation("org.yaml:snakeyaml:2.0") { + because("version 1.33 has vulnerabilities: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-1471.") + } + } + + implementation(libs.edc.spi.catalog) + implementation(libs.edc.spi.transactionspi) + implementation(libs.edc.spi.transaction.datasource) + implementation(libs.edc.dsp) + implementation(libs.edc.util) + implementation(libs.edc.sql.core) + implementation(libs.edc.sql.lease) + implementation(libs.edc.sql.pool) implementation(libs.postgres) implementation(libs.jakarta.rsApi) - implementation(edc.spi.aggregateservices) + implementation(libs.edc.spi.aggregateservices) testImplementation(libs.awaitility) } diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetriever.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetriever.java index 7d87948b0..e059bb695 100644 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetriever.java +++ b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetriever.java @@ -14,12 +14,6 @@ package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; -import java.time.Instant; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.spi.contractagreement.ContractAgreementService; @@ -28,51 +22,54 @@ import org.eclipse.edc.spi.query.Criterion; import org.eclipse.edc.spi.query.QuerySpec; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + @RequiredArgsConstructor public class ContractAgreementRetriever { - private final Monitor monitor; - private final ContractAgreementService agreementService; - - public ContractAgreement getExistingContractById(String contractAgreementId) { - return agreementService.findById(contractAgreementId); - } + private final Monitor monitor; + private final ContractAgreementService agreementService; - public ContractAgreement getExistingContractByAssetId(String assetId) { - Collection agreements = getContractAgreementsByAssetId(assetId); - - validateResults(agreements); + public ContractAgreement getExistingContractById(String contractAgreementId) { + return agreementService.findById(contractAgreementId); + } - long now = Instant.now().getEpochSecond(); - return agreements.stream() - .filter(agreement -> agreement.getContractStartDate() < now) - .filter(agreement -> agreement.getContractEndDate() > now) - .findFirst() - .orElse(null); - } + public ContractAgreement getExistingContractByAssetId(String assetId) { + Collection agreements = getContractAgreementsByAssetId(assetId); - private Collection getContractAgreementsByAssetId(String assetId) { - QuerySpec querySpec = - QuerySpec.Builder.newInstance() - .filter(List.of(new Criterion("policy.target", "=", assetId))) - .limit(500) - .build(); - ServiceResult> result = agreementService.query(querySpec); - return result.succeeded() - ? result.getContent().collect(Collectors.toList()) - : Collections.emptyList(); - } + validateResults(agreements); + // TODO this is not valid anymore since start and end date where removed from CA we cannot fetch a valid contract + return agreements.stream() + .findFirst() + .orElse(null); + } - private void validateResults(Collection agreements) { - if (agreements.size() > 1) { - monitor.warning( - "More than one agreement found for a given assetId! First of the list will be used!"); + private Collection getContractAgreementsByAssetId(String assetId) { + QuerySpec querySpec = + QuerySpec.Builder.newInstance() + .filter(List.of(new Criterion("policy.target", "=", assetId))) + .limit(500) + .build(); + ServiceResult> result = agreementService.query(querySpec); + return result.succeeded() + ? result.getContent().collect(Collectors.toList()) + : Collections.emptyList(); } - int numberOfProviders = - agreements.stream() - .collect(Collectors.groupingBy(ContractAgreement::getProviderAgentId)) - .size(); - if (numberOfProviders > 1) { - monitor.warning("Contract agreement: given assetId found for more than one provider!"); + + private void validateResults(Collection agreements) { + if (agreements.size() > 1) { + monitor.warning( + "More than one agreement found for a given assetId! First of the list will be used!"); + } + int numberOfProviders = + agreements.stream() + .collect(Collectors.groupingBy(ContractAgreement::getProviderId)) + .size(); + if (numberOfProviders > 1) { + monitor.warning("Contract agreement: given assetId found for more than one provider!"); + } } - } } diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandler.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandler.java index b0d7bf237..26d5b1755 100644 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandler.java +++ b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandler.java @@ -15,13 +15,12 @@ package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; import jakarta.ws.rs.core.Response; -import java.time.Instant; -import java.util.*; import lombok.RequiredArgsConstructor; import org.eclipse.edc.catalog.spi.Catalog; import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractOfferRequest; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequestData; import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; import org.eclipse.edc.spi.monitor.Monitor; @@ -32,113 +31,117 @@ import org.eclipse.tractusx.edc.cp.adapter.messaging.Listener; import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; +import java.time.Instant; +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; + @RequiredArgsConstructor public class ContractNegotiationHandler implements Listener { - private final Monitor monitor; - private final MessageBus messageBus; - private final ContractNegotiationService contractNegotiationService; - private final CatalogCachedRetriever catalogRetriever; - private final ContractAgreementRetriever agreementRetriever; - - @Override - public void process(DataReferenceRetrievalDto dto) { - monitor.info( - String.format( - "[%s] RequestHandler: input request: [%s]", dto.getTraceId(), dto.getPayload())); - ProcessData processData = dto.getPayload(); - - ContractAgreement contractAgreement = getContractAgreementById(dto); - if (Objects.nonNull(dto.getPayload().getContractAgreementId()) && contractAgreement == null) { - sendNotFoundErrorResult(dto, getAgreementNotFoundMessage(dto)); - return; + private final Monitor monitor; + private final MessageBus messageBus; + private final ContractNegotiationService contractNegotiationService; + private final CatalogCachedRetriever catalogRetriever; + private final ContractAgreementRetriever agreementRetriever; + + @Override + public void process(DataReferenceRetrievalDto dto) { + monitor.info( + String.format( + "[%s] RequestHandler: input request: [%s]", dto.getTraceId(), dto.getPayload())); + ProcessData processData = dto.getPayload(); + + ContractAgreement contractAgreement = getContractAgreementById(dto); + if (Objects.nonNull(dto.getPayload().getContractAgreementId()) && contractAgreement == null) { + sendNotFoundErrorResult(dto, getAgreementNotFoundMessage(dto)); + return; + } + + if (Objects.isNull(contractAgreement)) { + contractAgreement = + agreementRetriever.getExistingContractByAssetId(dto.getPayload().getAssetId()); + } + + if (Objects.nonNull(contractAgreement) && isContractValid(contractAgreement)) { + monitor.info( + String.format("[%s] existing ContractAgreement taken from EDC.", dto.getTraceId())); + dto.getPayload().setContractAgreementId(contractAgreement.getId()); + dto.getPayload().setContractConfirmed(true); + messageBus.send(Channel.CONTRACT_CONFIRMATION, dto); + return; + } + + ContractOffer contractOffer = + findContractOffer( + processData.getAssetId(), + processData.getProvider(), + processData.getCatalogExpiryTime()); + + if (Objects.isNull(contractOffer)) { + sendNotFoundErrorResult(dto, getContractNotFoundMessage(dto)); + return; + } + + String contractNegotiationId = + initializeContractNegotiation( + contractOffer, dto.getPayload().getProvider(), dto.getTraceId()); + dto.getPayload().setContractNegotiationId(contractNegotiationId); + + messageBus.send(Channel.CONTRACT_CONFIRMATION, dto); + } + + private ContractAgreement getContractAgreementById(DataReferenceRetrievalDto dto) { + return Optional.ofNullable(dto.getPayload().getContractAgreementId()) + .map(agreementRetriever::getExistingContractById) + .orElse(null); + } + + // TODO the validity has been moved to policies + private boolean isContractValid(ContractAgreement contractAgreement) { + long now = Instant.now().getEpochSecond(); + return Objects.nonNull(contractAgreement); } - if (Objects.isNull(contractAgreement)) { - contractAgreement = - agreementRetriever.getExistingContractByAssetId(dto.getPayload().getAssetId()); + private ContractOffer findContractOffer( + String assetId, String providerUrl, int catalogExpiryTime) { + Catalog catalog = catalogRetriever.getEntireCatalog(providerUrl, assetId, catalogExpiryTime); + return Optional.ofNullable(catalog.getContractOffers()).orElse(Collections.emptyList()).stream() + .filter(it -> it.getAssetId().equals(assetId)) + .findFirst() + .orElse(null); } - if (Objects.nonNull(contractAgreement) && isContractValid(contractAgreement)) { - monitor.info( - String.format("[%s] existing ContractAgreement taken from EDC.", dto.getTraceId())); - dto.getPayload().setContractAgreementId(contractAgreement.getId()); - dto.getPayload().setContractConfirmed(true); - messageBus.send(Channel.CONTRACT_CONFIRMATION, dto); - return; + private String initializeContractNegotiation( + ContractOffer contractOffer, String providerUrl, String traceId) { + monitor.info(String.format("[%s] RequestHandler: initiateNegotiation - start", traceId)); + + var requestData = ContractRequestData.Builder.newInstance() + .contractOffer(contractOffer) + .counterPartyAddress(providerUrl) + .connectorId("provider") + .protocol("ids-multipart") + .build(); + + var request = ContractRequest.Builder.newInstance().requestData(requestData).build(); + + ContractNegotiation contractNegotiation = + contractNegotiationService.initiateNegotiation(request); + monitor.info(String.format("[%s] RequestHandler: initiateNegotiation - end", traceId)); + return Optional.ofNullable(contractNegotiation.getId()) + .orElseThrow(() -> new ResourceNotFoundException("Could not find Contract NegotiationId")); } - ContractOffer contractOffer = - findContractOffer( - processData.getAssetId(), - processData.getProvider(), - processData.getCatalogExpiryTime()); + private void sendNotFoundErrorResult(DataReferenceRetrievalDto dto, String message) { + dto.getPayload().setErrorMessage(message); + dto.getPayload().setErrorStatus(Response.Status.NOT_FOUND); + messageBus.send(Channel.RESULT, dto); + } - if (Objects.isNull(contractOffer)) { - sendNotFoundErrorResult(dto, getContractNotFoundMessage(dto)); - return; + private String getAgreementNotFoundMessage(DataReferenceRetrievalDto dto) { + return "Not found the contract agreement with ID: " + dto.getPayload().getContractAgreementId(); } - String contractNegotiationId = - initializeContractNegotiation( - contractOffer, dto.getPayload().getProvider(), dto.getTraceId()); - dto.getPayload().setContractNegotiationId(contractNegotiationId); - - messageBus.send(Channel.CONTRACT_CONFIRMATION, dto); - } - - private ContractAgreement getContractAgreementById(DataReferenceRetrievalDto dto) { - return Optional.ofNullable(dto.getPayload().getContractAgreementId()) - .map(agreementRetriever::getExistingContractById) - .orElse(null); - } - - private boolean isContractValid(ContractAgreement contractAgreement) { - long now = Instant.now().getEpochSecond(); - return Objects.nonNull(contractAgreement) - && contractAgreement.getContractStartDate() < now - && contractAgreement.getContractEndDate() > now; - } - - private ContractOffer findContractOffer( - String assetId, String providerUrl, int catalogExpiryTime) { - Catalog catalog = catalogRetriever.getEntireCatalog(providerUrl, assetId, catalogExpiryTime); - return Optional.ofNullable(catalog.getContractOffers()).orElse(Collections.emptyList()).stream() - .filter(it -> it.getAsset().getId().equals(assetId)) - .findFirst() - .orElse(null); - } - - private String initializeContractNegotiation( - ContractOffer contractOffer, String providerUrl, String traceId) { - monitor.info(String.format("[%s] RequestHandler: initiateNegotiation - start", traceId)); - ContractOfferRequest contractOfferRequest = - ContractOfferRequest.Builder.newInstance() - .connectorAddress(providerUrl) - .contractOffer(contractOffer) - .type(ContractOfferRequest.Type.INITIAL) - .connectorId("provider") - .protocol("ids-multipart") - .correlationId(traceId) - .build(); - - ContractNegotiation contractNegotiation = - contractNegotiationService.initiateNegotiation(contractOfferRequest); - monitor.info(String.format("[%s] RequestHandler: initiateNegotiation - end", traceId)); - return Optional.ofNullable(contractNegotiation.getId()) - .orElseThrow(() -> new ResourceNotFoundException("Could not find Contract NegotiationId")); - } - - private void sendNotFoundErrorResult(DataReferenceRetrievalDto dto, String message) { - dto.getPayload().setErrorMessage(message); - dto.getPayload().setErrorStatus(Response.Status.NOT_FOUND); - messageBus.send(Channel.RESULT, dto); - } - - private String getAgreementNotFoundMessage(DataReferenceRetrievalDto dto) { - return "Not found the contract agreement with ID: " + dto.getPayload().getContractAgreementId(); - } - - private String getContractNotFoundMessage(DataReferenceRetrievalDto dto) { - return "Could not find Contract Offer for given Asset Id"; - } + private String getContractNotFoundMessage(DataReferenceRetrievalDto dto) { + return "Could not find Contract Offer for given Asset Id"; + } } diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerImpl.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerImpl.java index a441eddf6..cedfc69d4 100644 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerImpl.java +++ b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerImpl.java @@ -14,8 +14,6 @@ package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; -import static java.util.Objects.isNull; - import jakarta.ws.rs.core.Response; import lombok.RequiredArgsConstructor; import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationListener; @@ -25,64 +23,66 @@ import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; +import static java.util.Objects.isNull; + @RequiredArgsConstructor public class ContractNegotiationListenerImpl implements ContractNegotiationListener { - public static final String CONTRACT_DECLINED_MESSAGE = "Contract for asset is declined."; - public static final String CONTRACT_ERROR_MESSAGE = "Contract Error for asset."; - private final Monitor monitor; - private final MessageBus messageBus; - private final ContractNotificationSyncService syncService; - private final DataTransferInitializer dataTransfer; + public static final String CONTRACT_DECLINED_MESSAGE = "Contract for asset is declined."; + public static final String CONTRACT_ERROR_MESSAGE = "Contract Error for asset."; + private final Monitor monitor; + private final MessageBus messageBus; + private final ContractNotificationSyncService syncService; + private final DataTransferInitializer dataTransfer; - @Override - public void confirmed(ContractNegotiation negotiation) { - monitor.info("ContractConfirmationHandler: received ContractConfirmation event"); - String negotiationId = negotiation.getId(); - String agreementId = negotiation.getContractAgreement().getId(); - DataReferenceRetrievalDto dto = - syncService.exchangeConfirmedContract(negotiationId, agreementId); - if (isNull(dto)) { - return; + @Override + public void declined(ContractNegotiation negotiation) { + monitor.info("ContractConfirmationHandler: received ContractDeclined event"); + String contractNegotiationId = negotiation.getId(); + DataReferenceRetrievalDto dto = syncService.exchangeDeclinedContract(contractNegotiationId); + if (isNull(dto)) { + return; + } + sendErrorResult(dto, CONTRACT_DECLINED_MESSAGE); + syncService.removeDto(contractNegotiationId); } - dto.getPayload().setContractAgreementId(agreementId); - initiateDataTransfer(dto); - syncService.removeDto(negotiationId); - } - @Override - public void declined(ContractNegotiation negotiation) { - monitor.info("ContractConfirmationHandler: received ContractDeclined event"); - String contractNegotiationId = negotiation.getId(); - DataReferenceRetrievalDto dto = syncService.exchangeDeclinedContract(contractNegotiationId); - if (isNull(dto)) { - return; + @Override + public void failed(ContractNegotiation negotiation) { + monitor.info("ContractConfirmationHandler: received ContractError event"); + String contractNegotiationId = negotiation.getId(); + DataReferenceRetrievalDto dto = syncService.exchangeErrorContract(contractNegotiationId); + if (isNull(dto)) { + return; + } + sendErrorResult(dto, CONTRACT_ERROR_MESSAGE); + syncService.removeDto(contractNegotiationId); } - sendErrorResult(dto, CONTRACT_DECLINED_MESSAGE); - syncService.removeDto(contractNegotiationId); - } - @Override - public void failed(ContractNegotiation negotiation) { - monitor.info("ContractConfirmationHandler: received ContractError event"); - String contractNegotiationId = negotiation.getId(); - DataReferenceRetrievalDto dto = syncService.exchangeErrorContract(contractNegotiationId); - if (isNull(dto)) { - return; + @Override + public void finalized(ContractNegotiation negotiation) { + monitor.info("ContractConfirmationHandler: received ContractConfirmation event"); + String negotiationId = negotiation.getId(); + String agreementId = negotiation.getContractAgreement().getId(); + DataReferenceRetrievalDto dto = + syncService.exchangeConfirmedContract(negotiationId, agreementId); + if (isNull(dto)) { + return; + } + dto.getPayload().setContractAgreementId(agreementId); + initiateDataTransfer(dto); + syncService.removeDto(negotiationId); } - sendErrorResult(dto, CONTRACT_ERROR_MESSAGE); - syncService.removeDto(contractNegotiationId); - } - public void initiateDataTransfer(DataReferenceRetrievalDto dto) { - String transferProcessId = dataTransfer.initiate(dto); - dto.getPayload().setTransferProcessId(transferProcessId); - dto.getPayload().setContractConfirmed(true); - messageBus.send(Channel.DATA_REFERENCE, dto); - } + public void initiateDataTransfer(DataReferenceRetrievalDto dto) { + String transferProcessId = dataTransfer.initiate(dto); + dto.getPayload().setTransferProcessId(transferProcessId); + dto.getPayload().setContractConfirmed(true); + messageBus.send(Channel.DATA_REFERENCE, dto); + } - private void sendErrorResult(DataReferenceRetrievalDto dto, String errorMessage) { - dto.getPayload().setErrorMessage(errorMessage); - dto.getPayload().setErrorStatus(Response.Status.BAD_GATEWAY); - messageBus.send(Channel.RESULT, dto); - } + private void sendErrorResult(DataReferenceRetrievalDto dto, String errorMessage) { + dto.getPayload().setErrorMessage(errorMessage); + dto.getPayload().setErrorStatus(Response.Status.BAD_GATEWAY); + messageBus.send(Channel.RESULT, dto); + } } diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandler.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandler.java index 5bd254194..092e8ce01 100644 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandler.java +++ b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandler.java @@ -14,10 +14,6 @@ package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; -import static jakarta.ws.rs.core.Response.Status; -import static java.util.Objects.isNull; - -import java.util.Objects; import lombok.RequiredArgsConstructor; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates; @@ -28,65 +24,70 @@ import org.eclipse.tractusx.edc.cp.adapter.messaging.Listener; import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; +import java.util.Objects; + +import static jakarta.ws.rs.core.Response.Status; +import static java.util.Objects.isNull; + @RequiredArgsConstructor public class ContractNotificationHandler implements Listener { - public static final String CONTRACT_DECLINED_MESSAGE = "Contract for asset is declined."; - public static final String CONTRACT_ERROR_MESSAGE = "Contract Error for asset."; - private final Monitor monitor; - private final MessageBus messageBus; - private final ContractNotificationSyncService syncService; - private final ContractNegotiationService contractNegotiationService; - private final DataTransferInitializer dataTransfer; + public static final String CONTRACT_DECLINED_MESSAGE = "Contract for asset is declined."; + public static final String CONTRACT_ERROR_MESSAGE = "Contract Error for asset."; + private final Monitor monitor; + private final MessageBus messageBus; + private final ContractNotificationSyncService syncService; + private final ContractNegotiationService contractNegotiationService; + private final DataTransferInitializer dataTransfer; - @Override - public void process(DataReferenceRetrievalDto dto) { - monitor.info( - String.format("[%s] ContractConfirmationHandler: received message.", dto.getTraceId())); - String contractNegotiationId = dto.getPayload().getContractNegotiationId(); + @Override + public void process(DataReferenceRetrievalDto dto) { + monitor.info( + String.format("[%s] ContractConfirmationHandler: received message.", dto.getTraceId())); + String contractNegotiationId = dto.getPayload().getContractNegotiationId(); - if (dto.getPayload().isContractConfirmed()) { - initiateDataTransfer(dto); - return; - } + if (dto.getPayload().isContractConfirmed()) { + initiateDataTransfer(dto); + return; + } - ContractNegotiation contractNegotiation = - contractNegotiationService.findbyId(contractNegotiationId); - if (isContractConfirmed(contractNegotiation)) { - dto.getPayload().setContractAgreementId(contractNegotiation.getContractAgreement().getId()); - initiateDataTransfer(dto); - return; - } + ContractNegotiation contractNegotiation = + contractNegotiationService.findbyId(contractNegotiationId); + if (isContractConfirmed(contractNegotiation)) { + dto.getPayload().setContractAgreementId(contractNegotiation.getContractAgreement().getId()); + initiateDataTransfer(dto); + return; + } - ContractInfo contractInfo = syncService.exchangeDto(dto); - if (isNull(contractInfo)) { - return; - } + ContractInfo contractInfo = syncService.exchangeDto(dto); + if (isNull(contractInfo)) { + return; + } - if (contractInfo.isConfirmed()) { - dto.getPayload().setContractAgreementId(contractInfo.getContractAgreementId()); - initiateDataTransfer(dto); - } else { - sendErrorResult( - dto, contractInfo.isDeclined() ? CONTRACT_DECLINED_MESSAGE : CONTRACT_ERROR_MESSAGE); + if (contractInfo.isConfirmed()) { + dto.getPayload().setContractAgreementId(contractInfo.getContractAgreementId()); + initiateDataTransfer(dto); + } else { + sendErrorResult( + dto, contractInfo.isDeclined() ? CONTRACT_DECLINED_MESSAGE : CONTRACT_ERROR_MESSAGE); + } + syncService.removeContractInfo(contractNegotiationId); } - syncService.removeContractInfo(contractNegotiationId); - } - public void initiateDataTransfer(DataReferenceRetrievalDto dto) { - String transferProcessId = dataTransfer.initiate(dto); - dto.getPayload().setTransferProcessId(transferProcessId); - dto.getPayload().setContractConfirmed(true); - messageBus.send(Channel.DATA_REFERENCE, dto); - } + public void initiateDataTransfer(DataReferenceRetrievalDto dto) { + String transferProcessId = dataTransfer.initiate(dto); + dto.getPayload().setTransferProcessId(transferProcessId); + dto.getPayload().setContractConfirmed(true); + messageBus.send(Channel.DATA_REFERENCE, dto); + } - private void sendErrorResult(DataReferenceRetrievalDto dto, String errorMessage) { - dto.getPayload().setErrorMessage(errorMessage); - dto.getPayload().setErrorStatus(Status.BAD_GATEWAY); - messageBus.send(Channel.RESULT, dto); - } + private void sendErrorResult(DataReferenceRetrievalDto dto, String errorMessage) { + dto.getPayload().setErrorMessage(errorMessage); + dto.getPayload().setErrorStatus(Status.BAD_GATEWAY); + messageBus.send(Channel.RESULT, dto); + } - private boolean isContractConfirmed(ContractNegotiation contractNegotiation) { - return Objects.nonNull(contractNegotiation) - && contractNegotiation.getState() == ContractNegotiationStates.CONFIRMED.code(); - } + private boolean isContractConfirmed(ContractNegotiation contractNegotiation) { + return Objects.nonNull(contractNegotiation) + && contractNegotiation.getState() == ContractNegotiationStates.FINALIZED.code(); + } } diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/DataTransferInitializer.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/DataTransferInitializer.java index 34812567f..c4236d574 100644 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/DataTransferInitializer.java +++ b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/DataTransferInitializer.java @@ -17,8 +17,7 @@ import lombok.RequiredArgsConstructor; import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; import org.eclipse.edc.connector.transfer.spi.types.DataRequest; -import org.eclipse.edc.connector.transfer.spi.types.TransferType; -import org.eclipse.edc.service.spi.result.ServiceResult; +import org.eclipse.edc.connector.transfer.spi.types.TransferRequest; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; @@ -26,47 +25,42 @@ @RequiredArgsConstructor public class DataTransferInitializer { - private final Monitor monitor; - private final TransferProcessService transferProcessService; + private final Monitor monitor; + private final TransferProcessService transferProcessService; - public String initiate(DataReferenceRetrievalDto dto) { - monitor.info( - String.format( - "[%s] ContractConfirmationHandler: transfer init - start.", dto.getTraceId())); - DataAddress dataDestination = DataAddress.Builder.newInstance().type("HttpProxy").build(); + public String initiate(DataReferenceRetrievalDto dto) { + monitor.info( + String.format( + "[%s] ContractConfirmationHandler: transfer init - start.", dto.getTraceId())); + DataAddress dataDestination = DataAddress.Builder.newInstance().type("HttpProxy").build(); - TransferType transferType = - TransferType.Builder.transferType() - .contentType("application/octet-stream") - .isFinite(true) - .build(); + DataRequest dataRequest = + DataRequest.Builder.newInstance() + .id(dto.getTraceId()) + .assetId(dto.getPayload().getAssetId()) + .contractId(dto.getPayload().getContractAgreementId()) + .connectorId("provider") + .connectorAddress(dto.getPayload().getProvider()) + .protocol("ids-multipart") + .dataDestination(dataDestination) + .managedResources(false) + .build(); - DataRequest dataRequest = - DataRequest.Builder.newInstance() - .id(dto.getTraceId()) - .assetId(dto.getPayload().getAssetId()) - .contractId(dto.getPayload().getContractAgreementId()) - .connectorId("provider") - .connectorAddress(dto.getPayload().getProvider()) - .protocol("ids-multipart") - .dataDestination(dataDestination) - .managedResources(false) - .transferType(transferType) - .build(); + TransferRequest transferRequest = TransferRequest.Builder.newInstance().dataRequest(dataRequest).build(); - ServiceResult result = transferProcessService.initiateTransfer(dataRequest); - monitor.info( - String.format("[%s] ContractConfirmationHandler: transfer init - end", dto.getTraceId())); - if (result.failed()) { - throwDataRefRequestException(dto); - } + var result = transferProcessService.initiateTransfer(transferRequest); + monitor.info( + String.format("[%s] ContractConfirmationHandler: transfer init - end", dto.getTraceId())); + if (result.failed()) { + throwDataRefRequestException(dto); + } - return result.getContent(); - } + return result.getContent().getId(); + } - private void throwDataRefRequestException(DataReferenceRetrievalDto dto) { - throw new ExternalRequestException( - String.format( - "Data reference initial request failed! AssetId: %s", dto.getPayload().getAssetId())); - } + private void throwDataRefRequestException(DataReferenceRetrievalDto dto) { + throw new ExternalRequestException( + String.format( + "Data reference initial request failed! AssetId: %s", dto.getPayload().getAssetId())); + } } diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandler.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandler.java index 39885837c..91b5a8d21 100644 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandler.java +++ b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandler.java @@ -15,7 +15,6 @@ package org.eclipse.tractusx.edc.cp.adapter.process.datareference; import jakarta.ws.rs.core.Response; -import java.util.List; import lombok.AllArgsConstructor; import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; import org.eclipse.edc.spi.monitor.Monitor; @@ -26,33 +25,35 @@ import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreService; import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectType; +import java.util.List; + @AllArgsConstructor public class DataReferenceErrorHandler { - private static final String ERROR_MESSAGE = "Data reference process stage failed with status: "; - private final Monitor monitor; - private final MessageBus messageBus; - private final ObjectStoreService objectStore; - private final TransferProcessService transferProcessService; - - private final List errorStates = List.of("CANCELLED", "ERROR"); - - public void validateActiveProcesses() { - monitor.debug("Data reference error handling - START"); - objectStore.get(ObjectType.DTO, DataReferenceRetrievalDto.class).stream() - .filter(dto -> !StringUtils.isNullOrEmpty(dto.getPayload().getTransferProcessId())) - .forEach(this::validateProcess); - } - - private void validateProcess(DataReferenceRetrievalDto dto) { - String state = transferProcessService.getState(dto.getPayload().getTransferProcessId()); - if (errorStates.contains(state)) { - monitor.warning(String.format("[%s] ", dto.getTraceId()) + ERROR_MESSAGE + state); - String contractAgreementId = dto.getPayload().getContractAgreementId(); - objectStore.remove(contractAgreementId, ObjectType.DTO); - - dto.getPayload().setErrorStatus(Response.Status.BAD_GATEWAY); - dto.getPayload().setErrorMessage(ERROR_MESSAGE + state); - messageBus.send(Channel.RESULT, dto); + private static final String ERROR_MESSAGE = "Data reference process stage failed with status: "; + private final Monitor monitor; + private final MessageBus messageBus; + private final ObjectStoreService objectStore; + private final TransferProcessService transferProcessService; + + private final List errorStates = List.of("CANCELLED", "TERMINATED"); + + public void validateActiveProcesses() { + monitor.debug("Data reference error handling - START"); + objectStore.get(ObjectType.DTO, DataReferenceRetrievalDto.class).stream() + .filter(dto -> !StringUtils.isNullOrEmpty(dto.getPayload().getTransferProcessId())) + .forEach(this::validateProcess); + } + + private void validateProcess(DataReferenceRetrievalDto dto) { + String state = transferProcessService.getState(dto.getPayload().getTransferProcessId()); + if (errorStates.contains(state)) { + monitor.warning(String.format("[%s] ", dto.getTraceId()) + ERROR_MESSAGE + state); + String contractAgreementId = dto.getPayload().getContractAgreementId(); + objectStore.remove(contractAgreementId, ObjectType.DTO); + + dto.getPayload().setErrorStatus(Response.Status.BAD_GATEWAY); + dto.getPayload().setErrorMessage(ERROR_MESSAGE + state); + messageBus.send(Channel.RESULT, dto); + } } - } } diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetrieverTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetrieverTest.java index 22f1946ed..2de00a687 100644 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetrieverTest.java +++ b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetrieverTest.java @@ -14,79 +14,76 @@ package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; - -import java.time.ZonedDateTime; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import org.eclipse.edc.catalog.spi.Catalog; import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; import org.eclipse.edc.connector.spi.catalog.CatalogService; import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.types.domain.asset.Asset; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + public class CatalogRetrieverTest { - @Mock CatalogService catalogService; + @Mock + CatalogService catalogService; - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } + @BeforeEach + void init() { + MockitoAnnotations.openMocks(this); + } - @Test - public void getEntireCatalog_shouldReturnEntireCatalogIfMoreThanOnePage() { - // given - CatalogRetriever catalogRetriever = new CatalogRetriever(50, catalogService); - when(catalogService.getByProviderUrl(anyString(), any())) - .thenReturn(getCatalogResult(50), getCatalogResult(50), getCatalogResult(10)); + @Test + public void getEntireCatalog_shouldReturnEntireCatalogIfMoreThanOnePage() { + // given + CatalogRetriever catalogRetriever = new CatalogRetriever(50, catalogService); + when(catalogService.getByProviderUrl(anyString(), any())) + .thenReturn(getCatalogResult(50), getCatalogResult(50), getCatalogResult(10)); - // when - Catalog catalog = catalogRetriever.getEntireCatalog("providerUrl", "assetId"); + // when + Catalog catalog = catalogRetriever.getEntireCatalog("providerUrl", "assetId"); - // then - assertEquals(110, catalog.getContractOffers().size()); - } + // then + assertEquals(110, catalog.getContractOffers().size()); + } - @Test - public void getEntireCatalog_shouldEmptyCatalogIfNoResults() { - // given - CatalogRetriever catalogRetriever = new CatalogRetriever(50, catalogService); - when(catalogService.getByProviderUrl(anyString(), any())).thenReturn(getCatalogResult(0)); + @Test + public void getEntireCatalog_shouldEmptyCatalogIfNoResults() { + // given + CatalogRetriever catalogRetriever = new CatalogRetriever(50, catalogService); + when(catalogService.getByProviderUrl(anyString(), any())).thenReturn(getCatalogResult(0)); - // when - Catalog catalog = catalogRetriever.getEntireCatalog("providerUrl", "assetId"); + // when + Catalog catalog = catalogRetriever.getEntireCatalog("providerUrl", "assetId"); - // then - assertEquals(0, catalog.getContractOffers().size()); - } + // then + assertEquals(0, catalog.getContractOffers().size()); + } - private CompletableFuture getCatalogResult(int offersNumber) { - List contractOffers = - IntStream.range(0, offersNumber) - .mapToObj(operand -> getContractOffer()) - .collect(Collectors.toList()); + private CompletableFuture getCatalogResult(int offersNumber) { + List contractOffers = + IntStream.range(0, offersNumber) + .mapToObj(operand -> getContractOffer()) + .collect(Collectors.toList()); - return CompletableFuture.completedFuture( - Catalog.Builder.newInstance().id("id").contractOffers(contractOffers).build()); - } + return CompletableFuture.completedFuture( + Catalog.Builder.newInstance().id("id").contractOffers(contractOffers).build()); + } - private ContractOffer getContractOffer() { - Asset asset = Asset.Builder.newInstance().id("assetId").build(); - return ContractOffer.Builder.newInstance() - .id("id") - .asset(asset) - .policy(Policy.Builder.newInstance().build()) - .contractStart(ZonedDateTime.now()) - .contractEnd(ZonedDateTime.now().plusDays(1)) - .build(); - } + private ContractOffer getContractOffer() { + return ContractOffer.Builder.newInstance() + .id("id") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()) + .build(); + } } diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetrieverTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetrieverTest.java index ad4cda3c0..f522b7693 100644 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetrieverTest.java +++ b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetrieverTest.java @@ -14,11 +14,6 @@ package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import java.time.Instant; -import java.util.stream.Stream; import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.spi.contractagreement.ContractAgreementService; import org.eclipse.edc.policy.model.Policy; @@ -26,61 +21,69 @@ import org.eclipse.edc.spi.monitor.Monitor; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.time.Instant; +import java.util.stream.Stream; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + public class ContractAgreementRetrieverTest { - @Mock Monitor monitor; - @Mock ContractAgreementService agreementService; + @Mock + Monitor monitor; + @Mock + ContractAgreementService agreementService; - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } + @BeforeEach + void init() { + MockitoAnnotations.openMocks(this); + } - @Test - public void getExistingContractByAssetId_shouldReturnValidContract() { - // given - long now = Instant.now().getEpochSecond(); - when(agreementService.query(any())).thenReturn(getResult(now + 1000)); - ContractAgreementRetriever retriever = - new ContractAgreementRetriever(monitor, agreementService); + @Test + public void getExistingContractByAssetId_shouldReturnValidContract() { + // given + long now = Instant.now().getEpochSecond(); + when(agreementService.query(any())).thenReturn(getResult(now + 1000)); + ContractAgreementRetriever retriever = + new ContractAgreementRetriever(monitor, agreementService); - // when - ContractAgreement contractAgreement = retriever.getExistingContractByAssetId("id"); + // when + ContractAgreement contractAgreement = retriever.getExistingContractByAssetId("id"); - // then - Assertions.assertNotNull(contractAgreement); - } + // then + Assertions.assertNotNull(contractAgreement); + } - @Test - public void getExistingContractByAssetId_shouldNotReturnExpiredContract() { - // given - long now = Instant.now().getEpochSecond(); - when(agreementService.query(any())).thenReturn(getResult(now - 1000)); - ContractAgreementRetriever retriever = - new ContractAgreementRetriever(monitor, agreementService); + @Test + // TODO remove The validity has been moved to policy + @Disabled + public void getExistingContractByAssetId_shouldNotReturnExpiredContract() { + // given + long now = Instant.now().getEpochSecond(); + when(agreementService.query(any())).thenReturn(getResult(now - 1000)); + ContractAgreementRetriever retriever = + new ContractAgreementRetriever(monitor, agreementService); - // when - ContractAgreement contractAgreement = retriever.getExistingContractByAssetId("id"); + // when + ContractAgreement contractAgreement = retriever.getExistingContractByAssetId("id"); - // then - Assertions.assertNull(contractAgreement); - } + // then + Assertions.assertNull(contractAgreement); + } - private ServiceResult> getResult(long endDate) { - long now = Instant.now().getEpochSecond(); - return ServiceResult.success( - Stream.of( - ContractAgreement.Builder.newInstance() - .id("id") - .assetId("assetId") - .contractStartDate(now - 2000) - .contractEndDate(endDate) - .providerAgentId("providerId") - .consumerAgentId("consumerId") - .policy(Policy.Builder.newInstance().build()) - .build())); - } + private ServiceResult> getResult(long endDate) { + return ServiceResult.success( + Stream.of( + ContractAgreement.Builder.newInstance() + .id("id") + .assetId("assetId") + .providerId("providerId") + .consumerId("consumerId") + .policy(Policy.Builder.newInstance().build()) + .build())); + } } diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandlerTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandlerTest.java index 7d82766a9..be7965a35 100644 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandlerTest.java +++ b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandlerTest.java @@ -14,13 +14,6 @@ package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; - -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.List; import org.eclipse.edc.catalog.spi.Catalog; import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; @@ -28,148 +21,155 @@ import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.types.domain.asset.Asset; import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; import org.eclipse.tractusx.edc.cp.adapter.messaging.Message; import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + public class ContractNegotiationHandlerTest { - @Mock Monitor monitor; - @Mock MessageBus messageBus; - @Mock ContractNegotiationService contractNegotiationService; - @Mock CatalogCachedRetriever catalogRetriever; - @Mock ContractAgreementRetriever agreementRetriever; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void process_shouldNotInitializeContractNegotiationWhenContractAlreadyAvailable() { - // given - ContractNegotiationHandler contractNegotiationHandler = - new ContractNegotiationHandler( - monitor, messageBus, contractNegotiationService, catalogRetriever, agreementRetriever); - - when(agreementRetriever.getExistingContractByAssetId(anyString())) - .thenReturn(getValidContractAgreement()); - - // when - contractNegotiationHandler.process(new DataReferenceRetrievalDto(getProcessData(), 3)); - - // then - verify(contractNegotiationService, times(0)).initiateNegotiation(any()); - verify(messageBus, times(1)).send(any(), any(Message.class)); - } - - @Test - public void process_shouldInitializeContractNegotiationWhenExistingContractExpired() { - // given - ContractNegotiationHandler contractNegotiationHandler = - new ContractNegotiationHandler( - monitor, messageBus, contractNegotiationService, catalogRetriever, agreementRetriever); - - when(agreementRetriever.getExistingContractByAssetId(anyString())) - .thenReturn(getExpiredContractAgreement()); - when(catalogRetriever.getEntireCatalog(anyString(), anyString(), anyInt())) - .thenReturn(getCatalog()); - when(contractNegotiationService.initiateNegotiation(any())) - .thenReturn(getContractNegotiation()); - - // when - contractNegotiationHandler.process(new DataReferenceRetrievalDto(getProcessData(), 3)); - - // then - verify(contractNegotiationService, times(1)).initiateNegotiation(any()); - verify(messageBus, times(1)).send(any(), any(Message.class)); - } - - @Test - public void process_shouldInitiateContractNegotiationAndSendDtoFurtherIfAgreementNotExist() { - // given - ContractNegotiationHandler contractNegotiationHandler = - new ContractNegotiationHandler( - monitor, messageBus, contractNegotiationService, catalogRetriever, agreementRetriever); - - when(agreementRetriever.getExistingContractByAssetId(anyString())).thenReturn(null); - when(catalogRetriever.getEntireCatalog(anyString(), anyString(), anyInt())) - .thenReturn(getCatalog()); - when(contractNegotiationService.initiateNegotiation(any())) - .thenReturn(getContractNegotiation()); - - // when - contractNegotiationHandler.process(new DataReferenceRetrievalDto(getProcessData(), 3)); - - // then - verify(contractNegotiationService, times(1)).initiateNegotiation(any()); - verify(messageBus, times(1)).send(any(), any(Message.class)); - } - - private ProcessData getProcessData() { - return ProcessData.builder() - .assetId("assetId") - .provider("provider") - .catalogExpiryTime(30) - .contractAgreementReuseOn(true) - .build(); - } - - private ContractNegotiation getContractNegotiation() { - return ContractNegotiation.Builder.newInstance() - .id("contractNegotiationId") - .counterPartyId("counterPartyId") - .counterPartyAddress("counterPartyAddress") - .protocol("protocol") - .build(); - } - - private Catalog getCatalog() { - return Catalog.Builder.newInstance() - .id("id") - .contractOffers(List.of(getContractOffer())) - .build(); - } - - private ContractOffer getContractOffer() { - Asset asset = Asset.Builder.newInstance().id("assetId").build(); - return ContractOffer.Builder.newInstance() - .id("id") - .asset(asset) - .contractStart(ZonedDateTime.now()) - .contractEnd(ZonedDateTime.now().plusDays(1)) - .policy(Policy.Builder.newInstance().build()) - .build(); - } - - private ContractAgreement getValidContractAgreement() { - long now = Instant.now().getEpochSecond(); - return ContractAgreement.Builder.newInstance() - .id("id") - .assetId("assetId") - .contractStartDate(now - 5000) - .contractEndDate(now + 5000) - .consumerAgentId("consumer") - .providerAgentId("provider") - .policy(Policy.Builder.newInstance().build()) - .build(); - } - - private ContractAgreement getExpiredContractAgreement() { - long now = Instant.now().getEpochSecond(); - return ContractAgreement.Builder.newInstance() - .id("id") - .assetId("assetId") - .contractStartDate(now - 5000) - .contractEndDate(now - 1000) - .consumerAgentId("consumer") - .providerAgentId("provider") - .policy(Policy.Builder.newInstance().build()) - .build(); - } + @Mock + Monitor monitor; + @Mock + MessageBus messageBus; + @Mock + ContractNegotiationService contractNegotiationService; + @Mock + CatalogCachedRetriever catalogRetriever; + @Mock + ContractAgreementRetriever agreementRetriever; + + @BeforeEach + void init() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void process_shouldNotInitializeContractNegotiationWhenContractAlreadyAvailable() { + // given + ContractNegotiationHandler contractNegotiationHandler = + new ContractNegotiationHandler( + monitor, messageBus, contractNegotiationService, catalogRetriever, agreementRetriever); + + when(agreementRetriever.getExistingContractByAssetId(anyString())) + .thenReturn(getValidContractAgreement()); + + // when + contractNegotiationHandler.process(new DataReferenceRetrievalDto(getProcessData(), 3)); + + // then + verify(contractNegotiationService, times(0)).initiateNegotiation(any()); + verify(messageBus, times(1)).send(any(), any(Message.class)); + } + + @Test + // TODO remove this, the validity check has been moved to policies + @Disabled + public void process_shouldInitializeContractNegotiationWhenExistingContractExpired() { + // given + ContractNegotiationHandler contractNegotiationHandler = + new ContractNegotiationHandler( + monitor, messageBus, contractNegotiationService, catalogRetriever, agreementRetriever); + + when(agreementRetriever.getExistingContractByAssetId(anyString())) + .thenReturn(getExpiredContractAgreement()); + when(catalogRetriever.getEntireCatalog(anyString(), anyString(), anyInt())) + .thenReturn(getCatalog()); + when(contractNegotiationService.initiateNegotiation(any())) + .thenReturn(getContractNegotiation()); + + // when + contractNegotiationHandler.process(new DataReferenceRetrievalDto(getProcessData(), 3)); + + // then + verify(contractNegotiationService, times(1)).initiateNegotiation(any()); + verify(messageBus, times(1)).send(any(), any(Message.class)); + } + + @Test + public void process_shouldInitiateContractNegotiationAndSendDtoFurtherIfAgreementNotExist() { + // given + ContractNegotiationHandler contractNegotiationHandler = + new ContractNegotiationHandler( + monitor, messageBus, contractNegotiationService, catalogRetriever, agreementRetriever); + + when(agreementRetriever.getExistingContractByAssetId(anyString())).thenReturn(null); + when(catalogRetriever.getEntireCatalog(anyString(), anyString(), anyInt())) + .thenReturn(getCatalog()); + when(contractNegotiationService.initiateNegotiation(any())) + .thenReturn(getContractNegotiation()); + + // when + contractNegotiationHandler.process(new DataReferenceRetrievalDto(getProcessData(), 3)); + + // then + verify(contractNegotiationService, times(1)).initiateNegotiation(any()); + verify(messageBus, times(1)).send(any(), any(Message.class)); + } + + private ProcessData getProcessData() { + return ProcessData.builder() + .assetId("assetId") + .provider("provider") + .catalogExpiryTime(30) + .contractAgreementReuseOn(true) + .build(); + } + + private ContractNegotiation getContractNegotiation() { + return ContractNegotiation.Builder.newInstance() + .id("contractNegotiationId") + .counterPartyId("counterPartyId") + .counterPartyAddress("counterPartyAddress") + .protocol("protocol") + .build(); + } + + private Catalog getCatalog() { + return Catalog.Builder.newInstance() + .id("id") + .contractOffers(List.of(getContractOffer())) + .build(); + } + + private ContractOffer getContractOffer() { + return ContractOffer.Builder.newInstance() + .id("id") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()) + .build(); + } + + private ContractAgreement getValidContractAgreement() { + return ContractAgreement.Builder.newInstance() + .id("id") + .assetId("assetId") + .consumerId("consumer") + .providerId("provider") + .policy(Policy.Builder.newInstance().build()) + .build(); + } + + private ContractAgreement getExpiredContractAgreement() { + return ContractAgreement.Builder.newInstance() + .id("id") + .assetId("assetId") + .consumerId("consumer") + .providerId("provider") + .policy(Policy.Builder.newInstance().build()) + .build(); + } } diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerTest.java index 5d0e83553..c5d007e47 100644 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerTest.java +++ b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerTest.java @@ -14,11 +14,6 @@ package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.times; - import jakarta.ws.rs.core.Response; import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationListener; import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; @@ -38,111 +33,121 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + public class ContractNegotiationListenerTest { - @Mock Monitor monitor; - @Mock MessageBus messageBus; - @Mock ContractNotificationSyncService syncService; - @Mock DataTransferInitializer dataTransfer; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void confirmed_shouldNotInitiateTransferIfMessageNotAvailable() { - // given - ContractNegotiationListener listener = - new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); - ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); - - // when - listener.confirmed(contractNegotiation); - - // then - verify(syncService, times(1)).exchangeConfirmedContract(any(), any()); - verify(dataTransfer, times(0)).initiate(any()); - verify(messageBus, times(0)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - } - - @Test - public void confirmed_shouldInitiateTransferIfMessageIsAvailable() { - // given - when(syncService.exchangeConfirmedContract(any(), any())) - .thenReturn(new DataReferenceRetrievalDto(getProcessData(), 3)); - verify(dataTransfer, times(0)).initiate(any()); - - ContractNegotiationListener listener = - new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); - ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); - - // when - listener.confirmed(contractNegotiation); - - // then - verify(dataTransfer, times(1)).initiate(any()); - verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - } - - @Test - public void preDeclined_shouldSendErrorResultIfMessageIsAvailable() { - // given - when(syncService.exchangeDeclinedContract(any())) - .thenReturn(new DataReferenceRetrievalDto(getProcessData(), 3)); - ContractNegotiationListener listener = - new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); - ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); - - // when - listener.declined(contractNegotiation); - - // then - ArgumentCaptor messageArg = - ArgumentCaptor.forClass(DataReferenceRetrievalDto.class); - verify(messageBus, times(1)).send(eq(Channel.RESULT), messageArg.capture()); - Assertions.assertEquals( - Response.Status.BAD_GATEWAY, messageArg.getValue().getPayload().getErrorStatus()); - } - - @Test - public void preError_shouldSendErrorResultIfMessageIsAvailable() { - // given - when(syncService.exchangeErrorContract(any())) - .thenReturn(new DataReferenceRetrievalDto(getProcessData(), 3)); - ContractNegotiationListener listener = - new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); - ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); - - // when - listener.failed(contractNegotiation); - - // then - ArgumentCaptor messageArg = - ArgumentCaptor.forClass(DataReferenceRetrievalDto.class); - verify(messageBus, times(1)).send(eq(Channel.RESULT), messageArg.capture()); - Assertions.assertEquals( - Response.Status.BAD_GATEWAY, messageArg.getValue().getPayload().getErrorStatus()); - } - - private ContractNegotiation getConfirmedContractNegotiation() { - return ContractNegotiation.Builder.newInstance() - .state(ContractNegotiationStates.CONFIRMED.code()) - .id("contractNegotiationId") - .counterPartyId("counterPartyId") - .counterPartyAddress("counterPartyAddress") - .protocol("protocol") - .contractAgreement( - ContractAgreement.Builder.newInstance() - .id("contractAgreementId") - .providerAgentId("providerAgentId") - .consumerAgentId("consumerAgentId") - .assetId("assetId") - .policy(Policy.Builder.newInstance().build()) - .build()) - .build(); - } - - private ProcessData getProcessData() { - return ProcessData.builder().assetId("assetId").provider("provider").build(); - } + @Mock + Monitor monitor; + @Mock + MessageBus messageBus; + @Mock + ContractNotificationSyncService syncService; + @Mock + DataTransferInitializer dataTransfer; + + @BeforeEach + void init() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void confirmed_shouldNotInitiateTransferIfMessageNotAvailable() { + // given + ContractNegotiationListener listener = + new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); + ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); + + // when + listener.finalized(contractNegotiation); + + // then + verify(syncService, times(1)).exchangeConfirmedContract(any(), any()); + verify(dataTransfer, times(0)).initiate(any()); + verify(messageBus, times(0)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); + } + + @Test + public void confirmed_shouldInitiateTransferIfMessageIsAvailable() { + // given + when(syncService.exchangeConfirmedContract(any(), any())) + .thenReturn(new DataReferenceRetrievalDto(getProcessData(), 3)); + verify(dataTransfer, times(0)).initiate(any()); + + ContractNegotiationListener listener = + new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); + ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); + + // when + listener.finalized(contractNegotiation); + + // then + verify(dataTransfer, times(1)).initiate(any()); + verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); + } + + @Test + public void preDeclined_shouldSendErrorResultIfMessageIsAvailable() { + // given + when(syncService.exchangeDeclinedContract(any())) + .thenReturn(new DataReferenceRetrievalDto(getProcessData(), 3)); + ContractNegotiationListener listener = + new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); + ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); + + // when + listener.declined(contractNegotiation); + + // then + ArgumentCaptor messageArg = + ArgumentCaptor.forClass(DataReferenceRetrievalDto.class); + verify(messageBus, times(1)).send(eq(Channel.RESULT), messageArg.capture()); + Assertions.assertEquals( + Response.Status.BAD_GATEWAY, messageArg.getValue().getPayload().getErrorStatus()); + } + + @Test + public void preError_shouldSendErrorResultIfMessageIsAvailable() { + // given + when(syncService.exchangeErrorContract(any())) + .thenReturn(new DataReferenceRetrievalDto(getProcessData(), 3)); + ContractNegotiationListener listener = + new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); + ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); + + // when + listener.failed(contractNegotiation); + + // then + ArgumentCaptor messageArg = + ArgumentCaptor.forClass(DataReferenceRetrievalDto.class); + verify(messageBus, times(1)).send(eq(Channel.RESULT), messageArg.capture()); + Assertions.assertEquals( + Response.Status.BAD_GATEWAY, messageArg.getValue().getPayload().getErrorStatus()); + } + + private ContractNegotiation getConfirmedContractNegotiation() { + return ContractNegotiation.Builder.newInstance() + .state(ContractNegotiationStates.FINALIZED.code()) + .id("contractNegotiationId") + .counterPartyId("counterPartyId") + .counterPartyAddress("counterPartyAddress") + .protocol("protocol") + .contractAgreement( + ContractAgreement.Builder.newInstance() + .id("contractAgreementId") + .providerId("providerAgentId") + .consumerId("consumerAgentId") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()) + .build()) + .build(); + } + + private ProcessData getProcessData() { + return ProcessData.builder().assetId("assetId").provider("provider").build(); + } } diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandlerTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandlerTest.java index c2e0480b3..11976e0e8 100644 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandlerTest.java +++ b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandlerTest.java @@ -14,9 +14,6 @@ package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates; @@ -33,109 +30,120 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + public class ContractNotificationHandlerTest { - @Mock Monitor monitor; - @Mock MessageBus messageBus; - @Mock ContractNegotiationService contractNegotiationService; - @Mock ContractNotificationSyncService syncService; - @Mock DataTransferInitializer dataTransfer; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void process_shouldNotInitiateTransferWhenNoContractNotification() { - // given - ContractNotificationHandler contractNotificationHandler = - new ContractNotificationHandler( - monitor, messageBus, syncService, contractNegotiationService, dataTransfer); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - - // when - contractNotificationHandler.process(dto); - - // then - verify(syncService, times(1)).exchangeDto(any()); - verify(dataTransfer, times(0)).initiate(any()); - verify(messageBus, times(0)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - } - - @Test - public void process_shouldInitiateTransferWhenContractConfirmedFromCache() { - // given - ContractNotificationHandler contractNotificationHandler = - new ContractNotificationHandler( - monitor, messageBus, syncService, contractNegotiationService, dataTransfer); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - dto.getPayload().setContractConfirmed(true); - - // when - contractNotificationHandler.process(dto); - - // then - verify(dataTransfer, times(1)).initiate(any()); - verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - } - - @Test - public void process_shouldInitiateTransferWhenContractAlreadyConfirmedAtProvider() { - // given - when(contractNegotiationService.findbyId(any())).thenReturn(getConfirmedContractNegotiation()); - ContractNotificationHandler contractNotificationHandler = - new ContractNotificationHandler( - monitor, messageBus, syncService, contractNegotiationService, dataTransfer); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - - // when - contractNotificationHandler.process(dto); - - // then - verify(syncService, times(0)).exchangeDto(any()); - verify(dataTransfer, times(1)).initiate(any()); - verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - } - - @Test - public void process_shouldInitiateTransferWhenContractConfirmedByNotification() { - // given - when(syncService.exchangeDto(any())) - .thenReturn( - new ContractInfo("confirmedContractAgreementId", ContractInfo.ContractState.CONFIRMED)); - ContractNotificationHandler contractNotificationHandler = - new ContractNotificationHandler( - monitor, messageBus, syncService, contractNegotiationService, dataTransfer); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - - // when - contractNotificationHandler.process(dto); - - // then - verify(dataTransfer, times(1)).initiate(any()); - verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - verify(syncService, times(1)).removeContractInfo(any()); - } - - private ContractNegotiation getConfirmedContractNegotiation() { - return ContractNegotiation.Builder.newInstance() - .state(ContractNegotiationStates.CONFIRMED.code()) - .id("contractNegotiationId") - .counterPartyId("counterPartyId") - .counterPartyAddress("counterPartyAddress") - .protocol("protocol") - .contractAgreement( - ContractAgreement.Builder.newInstance() - .id("contractAgreementId") - .providerAgentId("providerAgentId") - .consumerAgentId("consumerAgentId") - .assetId("assetId") - .policy(Policy.Builder.newInstance().build()) - .build()) - .build(); - } - - private ProcessData getProcessData() { - return ProcessData.builder().assetId("assetId").provider("provider").build(); - } + @Mock + Monitor monitor; + @Mock + MessageBus messageBus; + @Mock + ContractNegotiationService contractNegotiationService; + @Mock + ContractNotificationSyncService syncService; + @Mock + DataTransferInitializer dataTransfer; + + @BeforeEach + void init() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void process_shouldNotInitiateTransferWhenNoContractNotification() { + // given + ContractNotificationHandler contractNotificationHandler = + new ContractNotificationHandler( + monitor, messageBus, syncService, contractNegotiationService, dataTransfer); + DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); + + // when + contractNotificationHandler.process(dto); + + // then + verify(syncService, times(1)).exchangeDto(any()); + verify(dataTransfer, times(0)).initiate(any()); + verify(messageBus, times(0)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); + } + + @Test + public void process_shouldInitiateTransferWhenContractConfirmedFromCache() { + // given + ContractNotificationHandler contractNotificationHandler = + new ContractNotificationHandler( + monitor, messageBus, syncService, contractNegotiationService, dataTransfer); + DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); + dto.getPayload().setContractConfirmed(true); + + // when + contractNotificationHandler.process(dto); + + // then + verify(dataTransfer, times(1)).initiate(any()); + verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); + } + + @Test + public void process_shouldInitiateTransferWhenContractAlreadyConfirmedAtProvider() { + // given + when(contractNegotiationService.findbyId(any())).thenReturn(getConfirmedContractNegotiation()); + ContractNotificationHandler contractNotificationHandler = + new ContractNotificationHandler( + monitor, messageBus, syncService, contractNegotiationService, dataTransfer); + DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); + + // when + contractNotificationHandler.process(dto); + + // then + verify(syncService, times(0)).exchangeDto(any()); + verify(dataTransfer, times(1)).initiate(any()); + verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); + } + + @Test + public void process_shouldInitiateTransferWhenContractConfirmedByNotification() { + // given + when(syncService.exchangeDto(any())) + .thenReturn( + new ContractInfo("confirmedContractAgreementId", ContractInfo.ContractState.CONFIRMED)); + ContractNotificationHandler contractNotificationHandler = + new ContractNotificationHandler( + monitor, messageBus, syncService, contractNegotiationService, dataTransfer); + DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); + + // when + contractNotificationHandler.process(dto); + + // then + verify(dataTransfer, times(1)).initiate(any()); + verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); + verify(syncService, times(1)).removeContractInfo(any()); + } + + private ContractNegotiation getConfirmedContractNegotiation() { + return ContractNegotiation.Builder.newInstance() + .state(ContractNegotiationStates.FINALIZED.code()) + .id("contractNegotiationId") + .counterPartyId("counterPartyId") + .counterPartyAddress("counterPartyAddress") + .protocol("protocol") + .contractAgreement( + ContractAgreement.Builder.newInstance() + .id("contractAgreementId") + .providerId("providerAgentId") + .consumerId("consumerAgentId") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()) + .build()) + .build(); + } + + private ProcessData getProcessData() { + return ProcessData.builder().assetId("assetId").provider("provider").build(); + } } diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandlerTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandlerTest.java index 167a47479..f8f480fb6 100644 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandlerTest.java +++ b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandlerTest.java @@ -14,10 +14,6 @@ package org.eclipse.tractusx.edc.cp.adapter.process.datareference; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; import org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates; @@ -35,76 +31,85 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + public class DataReferenceErrorHandlerTest { - @Mock Monitor monitor; - @Mock MessageBus messageBus; - @Mock TransferProcessService transferProcessService; - ObjectStoreService storeService = new ObjectStoreServiceInMemory(new ObjectMapper()); - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - storeService.put("key1", ObjectType.DTO, getDto()); - storeService.put("key2", ObjectType.DTO, getDto()); - } - - @Test - public void validateActiveProcesses_shouldSkipIfNoError() { - // given - when(transferProcessService.getState("transferId")) - .thenReturn(TransferProcessStates.COMPLETED.name()); - DataReferenceErrorHandler errorHandler = - new DataReferenceErrorHandler(monitor, messageBus, storeService, transferProcessService); - - // when - errorHandler.validateActiveProcesses(); - - // then - verify(messageBus, times(0)).send(eq(Channel.RESULT), any(Message.class)); - } - - @Test - public void validateActiveProcesses_shouldHandleErrorReference() { - // given - when(transferProcessService.getState("transferId")) - .thenReturn(TransferProcessStates.COMPLETED.name()) - .thenReturn(TransferProcessStates.ERROR.name()); - DataReferenceErrorHandler errorHandler = - new DataReferenceErrorHandler(monitor, messageBus, storeService, transferProcessService); - - // when - errorHandler.validateActiveProcesses(); - - // then - verify(messageBus, times(1)).send(eq(Channel.RESULT), any(Message.class)); - } - - @Test - public void validateActiveProcesses_shouldHandleCancelledReference() { - // given - when(transferProcessService.getState("transferId")) - .thenReturn(TransferProcessStates.COMPLETED.name()) - .thenReturn(TransferProcessStates.CANCELLED.name()); - DataReferenceErrorHandler errorHandler = - new DataReferenceErrorHandler(monitor, messageBus, storeService, transferProcessService); - - // when - errorHandler.validateActiveProcesses(); - - // then - verify(messageBus, times(1)).send(eq(Channel.RESULT), any(Message.class)); - } - - private DataReferenceRetrievalDto getDto() { - return new DataReferenceRetrievalDto(getProcessData(), 3); - } - - private ProcessData getProcessData() { - return ProcessData.builder() - .assetId("assetId") - .provider("provider") - .contractAgreementId("agreementId") - .transferProcessId("transferId") - .build(); - } + @Mock + Monitor monitor; + @Mock + MessageBus messageBus; + @Mock + TransferProcessService transferProcessService; + ObjectStoreService storeService = new ObjectStoreServiceInMemory(new ObjectMapper()); + + @BeforeEach + void init() { + MockitoAnnotations.openMocks(this); + storeService.put("key1", ObjectType.DTO, getDto()); + storeService.put("key2", ObjectType.DTO, getDto()); + } + + @Test + public void validateActiveProcesses_shouldSkipIfNoError() { + // given + when(transferProcessService.getState("transferId")) + .thenReturn(TransferProcessStates.COMPLETED.name()); + DataReferenceErrorHandler errorHandler = + new DataReferenceErrorHandler(monitor, messageBus, storeService, transferProcessService); + + // when + errorHandler.validateActiveProcesses(); + + // then + verify(messageBus, times(0)).send(eq(Channel.RESULT), any(Message.class)); + } + + @Test + public void validateActiveProcesses_shouldHandleErrorReference() { + // given + when(transferProcessService.getState("transferId")) + .thenReturn(TransferProcessStates.COMPLETED.name()) + .thenReturn(TransferProcessStates.TERMINATED.name()); + DataReferenceErrorHandler errorHandler = + new DataReferenceErrorHandler(monitor, messageBus, storeService, transferProcessService); + + // when + errorHandler.validateActiveProcesses(); + + // then + verify(messageBus, times(1)).send(eq(Channel.RESULT), any(Message.class)); + } + + @Test + public void validateActiveProcesses_shouldHandleCancelledReference() { + // given + when(transferProcessService.getState("transferId")) + .thenReturn(TransferProcessStates.COMPLETED.name()) + .thenReturn(TransferProcessStates.TERMINATED.name()); + DataReferenceErrorHandler errorHandler = + new DataReferenceErrorHandler(monitor, messageBus, storeService, transferProcessService); + + // when + errorHandler.validateActiveProcesses(); + + // then + verify(messageBus, times(1)).send(eq(Channel.RESULT), any(Message.class)); + } + + private DataReferenceRetrievalDto getDto() { + return new DataReferenceRetrievalDto(getProcessData(), 3); + } + + private ProcessData getProcessData() { + return ProcessData.builder() + .assetId("assetId") + .provider("provider") + .contractAgreementId("agreementId") + .transferProcessId("transferId") + .build(); + } } diff --git a/edc-extensions/cx-oauth2/build.gradle.kts b/edc-extensions/cx-oauth2/build.gradle.kts index 5d25556f0..16d6219ae 100644 --- a/edc-extensions/cx-oauth2/build.gradle.kts +++ b/edc-extensions/cx-oauth2/build.gradle.kts @@ -24,9 +24,9 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.spi.oauth2) - implementation(edc.spi.jwt) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.oauth2) + implementation(libs.edc.spi.jwt) implementation(libs.slf4j.api) implementation(libs.nimbus.jwt) implementation(libs.okhttp) diff --git a/edc-extensions/data-encryption/build.gradle.kts b/edc-extensions/data-encryption/build.gradle.kts index 8f5a67de0..79603708c 100644 --- a/edc-extensions/data-encryption/build.gradle.kts +++ b/edc-extensions/data-encryption/build.gradle.kts @@ -23,7 +23,7 @@ plugins { } dependencies { - api(edc.spi.core) - implementation(edc.spi.dataplane.transfer) - implementation(libs.bouncyCastle.bcpkix) + api(libs.edc.spi.core) + implementation(libs.edc.spi.dataplane.transfer) + implementation(libs.bouncyCastle.bcpkixJdk18on) } diff --git a/edc-extensions/dataplane-selector-configuration/build.gradle.kts b/edc-extensions/dataplane-selector-configuration/build.gradle.kts index ba7eff87d..bba41eac6 100644 --- a/edc-extensions/dataplane-selector-configuration/build.gradle.kts +++ b/edc-extensions/dataplane-selector-configuration/build.gradle.kts @@ -23,9 +23,9 @@ plugins { } dependencies { - api(edc.spi.core) - api(edc.junit) - implementation(edc.spi.dataplane.selector) - implementation(edc.spi.dataplane.transfer) - implementation(libs.bouncyCastle.bcpkix) + api(libs.edc.spi.core) + api(libs.edc.junit) + implementation(libs.edc.spi.dataplane.selector) + implementation(libs.edc.spi.dataplane.transfer) + implementation(libs.bouncyCastle.bcpkixJdk18on) } diff --git a/edc-extensions/hashicorp-vault/build.gradle.kts b/edc-extensions/hashicorp-vault/build.gradle.kts index 20d5c0aa4..caa93d104 100644 --- a/edc-extensions/hashicorp-vault/build.gradle.kts +++ b/edc-extensions/hashicorp-vault/build.gradle.kts @@ -23,9 +23,9 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.junit) - implementation(libs.bouncyCastle.bcpkix) + implementation(libs.edc.spi.core) + implementation(libs.edc.junit) + implementation(libs.bouncyCastle.bcpkixJdk18on) implementation(libs.okhttp) implementation("org.testcontainers:vault:1.18.1") implementation("org.testcontainers:junit-jupiter:1.18.1") diff --git a/edc-extensions/observability-api-customization/build.gradle.kts b/edc-extensions/observability-api-customization/build.gradle.kts index 33f1920d1..eb9141e23 100644 --- a/edc-extensions/observability-api-customization/build.gradle.kts +++ b/edc-extensions/observability-api-customization/build.gradle.kts @@ -24,19 +24,19 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.spi.web) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.web) // provides the web server - runtimeOnly(edc.ext.http) + runtimeOnly(libs.edc.ext.http) - testImplementation(edc.junit) + testImplementation(libs.edc.junit) testImplementation(libs.restAssured) // needed for auto-registering the Auth Service: - testImplementation(edc.api.management) + testImplementation(libs.edc.api.management) // provides token-based authentication at test runtime - testRuntimeOnly(edc.auth.tokenbased) + testRuntimeOnly(libs.edc.auth.tokenbased) } diff --git a/edc-extensions/postgresql-migration/build.gradle.kts b/edc-extensions/postgresql-migration/build.gradle.kts index 5b90d57df..8dfa8bc7c 100644 --- a/edc-extensions/postgresql-migration/build.gradle.kts +++ b/edc-extensions/postgresql-migration/build.gradle.kts @@ -23,11 +23,11 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.junit) - implementation(edc.spi.transaction.datasource) - implementation(edc.sql.assetindex) - implementation(edc.sql.core) + implementation(libs.edc.spi.core) + implementation(libs.edc.junit) + implementation(libs.edc.spi.transaction.datasource) + implementation(libs.edc.sql.assetindex) + implementation(libs.edc.sql.core) implementation("org.flywaydb:flyway-core:9.18.0") } diff --git a/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_5__Alter_ContractNegotiation_Add_Callbacks.sql b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_5__Alter_ContractNegotiation_Add_Callbacks.sql new file mode 100644 index 000000000..2caff7099 --- /dev/null +++ b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_5__Alter_ContractNegotiation_Add_Callbacks.sql @@ -0,0 +1,15 @@ +-- +-- Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +-- + +-- add column +ALTER TABLE edc_contract_negotiation ADD COLUMN callback_addresses JSON; diff --git a/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_7__Alter_TransferProcess_Add_Callbacks.sql b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_7__Alter_TransferProcess_Add_Callbacks.sql new file mode 100644 index 000000000..0ef0dd9d1 --- /dev/null +++ b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_7__Alter_TransferProcess_Add_Callbacks.sql @@ -0,0 +1,15 @@ +-- +-- Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +-- + +-- add column +ALTER TABLE edc_transfer_process ADD COLUMN callback_addresses JSON; diff --git a/edc-extensions/provision-additional-headers/build.gradle.kts b/edc-extensions/provision-additional-headers/build.gradle.kts index 1ac640a88..a8d00caed 100644 --- a/edc-extensions/provision-additional-headers/build.gradle.kts +++ b/edc-extensions/provision-additional-headers/build.gradle.kts @@ -23,12 +23,15 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.spi.transfer) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.transfer) testImplementation(libs.awaitility) - testImplementation(edc.junit) + testImplementation(libs.edc.junit) - testImplementation(edc.core.controlplane) + testImplementation(libs.edc.core.controlplane) + testImplementation(libs.edc.dpf.selector.core) + testImplementation(libs.edc.dsp) + testImplementation(libs.edc.iam.mock) testImplementation(libs.mockito.inline) } diff --git a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java b/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java index 72d68b631..1e3f72754 100644 --- a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java +++ b/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java @@ -20,21 +20,33 @@ package org.eclipse.tractusx.edc.provision.additionalheaders; -import org.eclipse.edc.connector.transfer.spi.TransferProcessManager; +import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; +import org.eclipse.edc.connector.contract.spi.validation.ContractValidationService; +import org.eclipse.edc.connector.spi.transferprocess.TransferProcessProtocolService; import org.eclipse.edc.connector.transfer.spi.flow.DataFlowController; import org.eclipse.edc.connector.transfer.spi.flow.DataFlowManager; -import org.eclipse.edc.connector.transfer.spi.types.DataRequest; +import org.eclipse.edc.connector.transfer.spi.types.DataFlowResponse; +import org.eclipse.edc.connector.transfer.spi.types.protocol.TransferRequestMessage; import org.eclipse.edc.junit.annotations.ComponentTest; import org.eclipse.edc.junit.extensions.EdcExtension; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.service.spi.result.ServiceResult; import org.eclipse.edc.spi.asset.AssetIndex; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; import org.eclipse.edc.spi.response.StatusResult; +import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.spi.types.domain.asset.Asset; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.any; import static org.mockito.Mockito.argThat; @@ -46,46 +58,62 @@ @ExtendWith(EdcExtension.class) class ProvisionAdditionalHeadersExtensionTest { - private final DataFlowController dataFlowController = mock(DataFlowController.class); - - @BeforeEach - void setUp() { - when(dataFlowController.canHandle(any(), any())).thenReturn(true); - when(dataFlowController.initiateFlow(any(), any(), any())).thenReturn(StatusResult.success()); - } - - @Test - void shouldPutContractIdAsHeaderInDataAddress( - TransferProcessManager transferProcessManager, - AssetIndex assetIndex, - DataFlowManager dataFlowManager) { - dataFlowManager.register(dataFlowController); - var asset = Asset.Builder.newInstance().id("assetId").build(); - var dataAddress = DataAddress.Builder.newInstance().type("HttpData").build(); - assetIndex.accept(asset, dataAddress); - - var dataRequest = - DataRequest.Builder.newInstance() - .contractId("aContractId") - .assetId("assetId") - .destinationType("HttpProxy") - .build(); - - var result = transferProcessManager.initiateProviderRequest(dataRequest); - - assertThat(result).matches(StatusResult::succeeded); - - await() - .untilAsserted( - () -> { - verify(dataFlowController) - .initiateFlow( - any(), - argThat( - it -> - "aContractId" - .equals(it.getProperty("header:Edc-Contract-Agreement-Id"))), - any()); - }); - } + private final DataFlowController dataFlowController = mock(DataFlowController.class); + private final RemoteMessageDispatcherRegistry dispatcherRegistry = mock(RemoteMessageDispatcherRegistry.class); + + private ContractNegotiationStore contractNegotiationStore = mock(ContractNegotiationStore.class); + private ContractValidationService contractValidationService = mock(ContractValidationService.class); + + @BeforeEach + void setUp(EdcExtension extension) { + extension.setConfiguration(Map.of("edc.ids.id", "urn:connector:test")); + when(dataFlowController.canHandle(any(), any())).thenReturn(true); + when(dataFlowController.initiateFlow(any(), any(), any())).thenReturn(StatusResult.success(DataFlowResponse.Builder.newInstance().build())); + extension.registerServiceMock(RemoteMessageDispatcherRegistry.class, dispatcherRegistry); + extension.registerServiceMock(ContractNegotiationStore.class, contractNegotiationStore); + extension.registerServiceMock(ContractValidationService.class, contractValidationService); + } + + @Test + void shouldPutContractIdAsHeaderInDataAddress( + TransferProcessProtocolService transferProcessProtocolService, + AssetIndex assetIndex, + DataFlowManager dataFlowManager) { + + var agreement = ContractAgreement.Builder.newInstance() + .id("aContractId") + .providerId("provider") + .consumerId("consumer") + .policy(Policy.Builder.newInstance().build()) + .assetId("assetId") + .build(); + + when(dispatcherRegistry.send(any(), any())).thenReturn(CompletableFuture.completedFuture(null)); + when(contractNegotiationStore.findContractAgreement(any())).thenReturn(agreement); + when(contractValidationService.validateAgreement(any(), any())).thenReturn(Result.success(agreement)); + + dataFlowManager.register(dataFlowController); + var asset = Asset.Builder.newInstance().id("assetId").build(); + var dataAddress = DataAddress.Builder.newInstance().type("HttpData").build(); + assetIndex.create(asset, dataAddress); + + var transferMessage = TransferRequestMessage.Builder.newInstance() + .id("id") + .protocol("protocol") + .assetId("assetId") + .contractId("aContractId") + .dataDestination(DataAddress.Builder.newInstance().type("HttpProxy").build()) + .callbackAddress("callbackAddress") + .build(); + + var result = transferProcessProtocolService.notifyRequested(transferMessage, ClaimToken.Builder.newInstance().build()); + + assertThat(result).matches(ServiceResult::succeeded); + + await().untilAsserted( + () -> { + verify(dataFlowController) + .initiateFlow(any(), argThat(it -> "aContractId".equals(it.getProperty("header:Edc-Contract-Agreement-Id"))), any()); + }); + } } diff --git a/edc-extensions/transferprocess-sftp-client/build.gradle.kts b/edc-extensions/transferprocess-sftp-client/build.gradle.kts index 67ae01bfb..7258805b8 100644 --- a/edc-extensions/transferprocess-sftp-client/build.gradle.kts +++ b/edc-extensions/transferprocess-sftp-client/build.gradle.kts @@ -24,20 +24,20 @@ plugins { dependencies { implementation(project(":edc-extensions:transferprocess-sftp-common")) - implementation(edc.spi.core) - implementation(edc.spi.transfer) - implementation(edc.spi.policy) - implementation(edc.spi.dataplane.dataplane) - implementation(edc.dpf.util) - implementation(edc.dpf.core) - implementation(edc.policy.engine) - implementation(libs.bouncyCastle.bcpkix) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.transfer) + implementation(libs.edc.spi.policy) + implementation(libs.edc.spi.dataplane.dataplane) + implementation(libs.edc.dpf.util) + implementation(libs.edc.dpf.core) + implementation(libs.edc.policy.engine) + implementation(libs.bouncyCastle.bcpkixJdk18on) implementation(libs.apache.sshd.core) implementation(libs.apache.sshd.sftp) testImplementation(libs.awaitility) - testImplementation(edc.junit) + testImplementation(libs.edc.junit) testImplementation(libs.mockito.inline) testImplementation(libs.testcontainers.junit) diff --git a/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSink.java b/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSink.java index e819229f0..f4a68f06f 100644 --- a/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSink.java +++ b/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSink.java @@ -14,28 +14,29 @@ package org.eclipse.tractusx.edc.transferprocess.sftp.client; -import java.io.IOException; -import java.util.List; import lombok.Builder; import lombok.NonNull; import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSource; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; import org.eclipse.edc.connector.dataplane.util.sink.ParallelSink; -import org.eclipse.edc.spi.response.ResponseStatus; -import org.eclipse.edc.spi.response.StatusResult; + +import java.io.IOException; +import java.util.List; @Builder public class SftpDataSink extends ParallelSink { - @NonNull private final SftpClientWrapper sftpClientWrapper; + @NonNull + private final SftpClientWrapper sftpClientWrapper; - @Override - protected StatusResult transferParts(List parts) { - for (DataSource.Part part : parts) { - try { - sftpClientWrapper.uploadFile(part.openStream()); - } catch (IOException e) { - return StatusResult.failure(ResponseStatus.FATAL_ERROR, e.getMessage()); - } + @Override + protected StreamResult transferParts(List parts) { + for (DataSource.Part part : parts) { + try { + sftpClientWrapper.uploadFile(part.openStream()); + } catch (IOException e) { + return StreamResult.error(e.getMessage()); + } + } + return StreamResult.success(); } - return StatusResult.success(); - } } diff --git a/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSource.java b/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSource.java index 334538fea..9020fb905 100644 --- a/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSource.java +++ b/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSource.java @@ -14,18 +14,21 @@ package org.eclipse.tractusx.edc.transferprocess.sftp.client; -import java.util.stream.Stream; import lombok.Builder; import lombok.NonNull; import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSource; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; + +import java.util.stream.Stream; @Builder public class SftpDataSource implements DataSource { - @NonNull private final SftpClientWrapper sftpClientWrapper; + @NonNull + private final SftpClientWrapper sftpClientWrapper; - @Override - public Stream openPartStream() { - Part sftpPart = new SftpPart(sftpClientWrapper); - return Stream.of(sftpPart); - } + @Override + public StreamResult> openPartStream() { + Part sftpPart = new SftpPart(sftpClientWrapper); + return StreamResult.success(Stream.of(sftpPart)); + } } diff --git a/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSourceTest.java b/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSourceTest.java index 7288c6442..100961640 100644 --- a/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSourceTest.java +++ b/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSourceTest.java @@ -20,10 +20,6 @@ package org.eclipse.tractusx.edc.transferprocess.sftp.client; -import java.io.ByteArrayInputStream; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; import lombok.SneakyThrows; import org.apache.sshd.sftp.client.SftpClient; import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSource; @@ -33,31 +29,36 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.io.ByteArrayInputStream; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + class SftpDataSourceTest { - @Test - @SneakyThrows - void openPartStream() { - final SftpUser userMock = Mockito.mock(SftpUser.class); - final SftpLocation locationMock = Mockito.mock(SftpLocation.class); - final SftpClientConfig sftpClientConfig = - SftpClientConfig.builder().sftpUser(userMock).sftpLocation(locationMock).build(); - final SftpClient sftpClientMock = Mockito.mock(SftpClient.class); - final SftpClientWrapperImpl sftpClientWrapper = - Mockito.spy(new SftpClientWrapperImpl(sftpClientConfig, sftpClientMock)); - SftpDataSource sftpDataSource = Mockito.spy(new SftpDataSource(sftpClientWrapper)); - byte[] expected = new byte[] {0, 1, 2, 3}; - ByteArrayInputStream outputStream = new ByteArrayInputStream(expected); + @Test + @SneakyThrows + void openPartStream() { + final SftpUser userMock = Mockito.mock(SftpUser.class); + final SftpLocation locationMock = Mockito.mock(SftpLocation.class); + final SftpClientConfig sftpClientConfig = + SftpClientConfig.builder().sftpUser(userMock).sftpLocation(locationMock).build(); + final SftpClient sftpClientMock = Mockito.mock(SftpClient.class); + final SftpClientWrapperImpl sftpClientWrapper = + Mockito.spy(new SftpClientWrapperImpl(sftpClientConfig, sftpClientMock)); + SftpDataSource sftpDataSource = Mockito.spy(new SftpDataSource(sftpClientWrapper)); + byte[] expected = new byte[]{ 0, 1, 2, 3 }; + ByteArrayInputStream outputStream = new ByteArrayInputStream(expected); - Mockito.when(locationMock.getPath()).thenReturn("path"); - Mockito.when( - sftpClientMock.read(Mockito.anyString(), Mockito.anyInt(), Mockito.anyCollection())) - .thenReturn(outputStream); + Mockito.when(locationMock.getPath()).thenReturn("path"); + Mockito.when( + sftpClientMock.read(Mockito.anyString(), Mockito.anyInt(), Mockito.anyCollection())) + .thenReturn(outputStream); - Stream partStream = sftpDataSource.openPartStream(); - DataSource.Part part = partStream.collect(Collectors.toList()).get(0); + Stream partStream = sftpDataSource.openPartStream().getContent(); + DataSource.Part part = partStream.collect(Collectors.toList()).get(0); - Assertions.assertArrayEquals(expected, part.openStream().readAllBytes()); - Mockito.verify(sftpClientMock, Mockito.times(1)) - .read("path", 4096, List.of(SftpClient.OpenMode.Read)); - } + Assertions.assertArrayEquals(expected, part.openStream().readAllBytes()); + Mockito.verify(sftpClientMock, Mockito.times(1)) + .read("path", 4096, List.of(SftpClient.OpenMode.Read)); + } } diff --git a/edc-extensions/transferprocess-sftp-common/build.gradle.kts b/edc-extensions/transferprocess-sftp-common/build.gradle.kts index b102c23c1..66a63d9ee 100644 --- a/edc-extensions/transferprocess-sftp-common/build.gradle.kts +++ b/edc-extensions/transferprocess-sftp-common/build.gradle.kts @@ -23,8 +23,8 @@ plugins { } dependencies { - implementation(edc.spi.core) - testImplementation(edc.junit) + implementation(libs.edc.spi.core) + testImplementation(libs.edc.junit) testImplementation(libs.mockito.inline) testImplementation(libs.testcontainers.junit) diff --git a/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts b/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts index bf239f371..b413b0951 100644 --- a/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts +++ b/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts @@ -25,11 +25,11 @@ plugins { dependencies { implementation(project(":edc-extensions:transferprocess-sftp-common")) - implementation(edc.spi.core) - implementation(edc.policy.engine) - implementation(edc.spi.transfer) + implementation(libs.edc.spi.core) + implementation(libs.edc.policy.engine) + implementation(libs.edc.spi.transfer) - testImplementation(edc.junit) + testImplementation(libs.edc.junit) testImplementation(libs.mockito.inline) testImplementation(libs.testcontainers.junit) } diff --git a/edc-tests/cucumber/build.gradle.kts b/edc-tests/cucumber/build.gradle.kts index d364320b3..cfdf0b480 100644 --- a/edc-tests/cucumber/build.gradle.kts +++ b/edc-tests/cucumber/build.gradle.kts @@ -40,12 +40,12 @@ dependencies { testImplementation("org.junit.platform:junit-platform-suite:1.9.3") testImplementation("io.cucumber:cucumber-java:7.12.0") testImplementation("io.cucumber:cucumber-junit-platform-engine:7.12.0") - testImplementation("org.slf4j:slf4j-api:2.0.7") + testImplementation(libs.slf4j.api) testImplementation(libs.restAssured) testImplementation(libs.postgres) testImplementation(libs.awaitility) testImplementation(libs.aws.s3) - testImplementation(edc.spi.core) + testImplementation(libs.edc.spi.core) } tasks.withType(Test::class) { diff --git a/edc-tests/e2e-tests/build.gradle.kts b/edc-tests/e2e-tests/build.gradle.kts index d716d89c2..237c134d9 100644 --- a/edc-tests/e2e-tests/build.gradle.kts +++ b/edc-tests/e2e-tests/build.gradle.kts @@ -17,22 +17,24 @@ plugins { } dependencies { - testImplementation("com.squareup.okhttp3:mockwebserver:5.0.0-alpha.11") + testImplementation(project(":edc-extensions:control-plane-adapter-api")) + testImplementation(libs.okhttp.mockwebserver) testImplementation(libs.restAssured) testImplementation(libs.postgres) testImplementation(libs.awaitility) testImplementation(libs.aws.s3) - testImplementation(edc.spi.core) - testImplementation(edc.junit) - testImplementation(edc.spi.policy) - testImplementation(edc.spi.contract) - testImplementation(edc.core.api) - testImplementation(edc.spi.catalog) - testImplementation(edc.api.catalog) - testImplementation(edc.api.contractnegotiation) - testImplementation(edc.api.transferprocess) - testImplementation(edc.spi.dataplane.selector) - + testImplementation(libs.edc.spi.core) + testImplementation(libs.edc.junit) + testImplementation(libs.edc.spi.policy) + testImplementation(libs.edc.spi.contract) + testImplementation(libs.edc.core.api) + testImplementation(libs.edc.spi.catalog) + testImplementation(libs.edc.api.catalog) + testImplementation(libs.edc.api.contractnegotiation) + testImplementation(libs.edc.api.transferprocess) + testImplementation(libs.edc.spi.dataplane.selector) + testImplementation(libs.edc.ext.jsonld) + testImplementation(libs.edc.dsp) } // do not publish diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/AssetHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/AssetHelperFunctions.java new file mode 100644 index 000000000..74a24e9b2 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/AssetHelperFunctions.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; + +public class AssetHelperFunctions { + + /** + * Creates an asset with the given ID and props using the participant's Data Management API + */ + public static JsonObject createAsset(String id, JsonObject assetProperties, JsonObject dataAddress) { + return Json.createObjectBuilder() + .add(CONTEXT, createContextBuilder()) + .add(TYPE, EDC_NAMESPACE + "AssetEntryDto") + .add(EDC_NAMESPACE + "asset", Json.createObjectBuilder() + .add(ID, id) + .add(EDC_NAMESPACE + "properties", assetProperties) + .build()) + .add(EDC_NAMESPACE + "dataAddress", dataAddress) + .build(); + + + } + + public static JsonObjectBuilder createDataAddressBuilder(String type) { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "DataAddress") + .add(EDC_NAMESPACE + "type", type); + } + + public static JsonObjectBuilder createContextBuilder() { + return Json.createObjectBuilder() + .add(EDC_PREFIX, EDC_NAMESPACE); + } + +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/CatalogHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/CatalogHelperFunctions.java new file mode 100644 index 000000000..3803ea9e3 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/CatalogHelperFunctions.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.edc.connector.contract.spi.ContractId; + +import static org.eclipse.edc.catalog.spi.CatalogRequest.EDC_CATALOG_REQUEST_QUERY_SPEC; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_POLICY_ATTRIBUTE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public class CatalogHelperFunctions { + + + public static JsonObject createCatalogRequest(JsonObject query, String dspEndpoint) { + var jsonBuilder = Json.createObjectBuilder(); + jsonBuilder.add("@type", "CatalogRequest"); + jsonBuilder.add(EDC_NAMESPACE + "providerUrl", dspEndpoint); + jsonBuilder.add(EDC_NAMESPACE + "protocol", "dataspace-protocol-http"); + + if (query != null) { + jsonBuilder.add(EDC_CATALOG_REQUEST_QUERY_SPEC, query); + } + return jsonBuilder.build(); + } + + public static ContractId getDatasetContractId(JsonObject dataset) { + var id = dataset.getJsonArray(ODRL_POLICY_ATTRIBUTE).get(0).asJsonObject().getString(ID); + return ContractId.parse(id); + } + + public static String getDatasetAssetId(JsonObject dataset) { + return getDatasetContractId(dataset).assetIdPart(); + } + + public static String getDatasetAssetId(JsonValue dataset) { + return getDatasetContractId(dataset.asJsonObject()).assetIdPart(); + } + + public static JsonArray getDatasetPolicies(JsonObject dataset) { + return dataset.getJsonArray(ODRL_POLICY_ATTRIBUTE); + } + + public static JsonObject getDatasetFirstPolicy(JsonObject dataset) { + return dataset.getJsonArray(ODRL_POLICY_ATTRIBUTE).stream().findFirst().get().asJsonObject(); + } + + public static JsonObject getDatasetFirstPolicy(JsonValue dataset) { + return getDatasetFirstPolicy(dataset.asJsonObject()); + } + + public static JsonArray getDatasetPolicies(JsonValue dataset) { + return getDatasetPolicies(dataset.asJsonObject()); + } +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractDefinitionHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractDefinitionHelperFunctions.java new file mode 100644 index 000000000..4377949ce --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractDefinitionHelperFunctions.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonObject; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public class ContractDefinitionHelperFunctions { + + public static JsonObject createContractDefinition(String assetId, String definitionId, String accessPolicyId, String contractPolicyId) { + return Json.createObjectBuilder() + .add(ID, definitionId) + .add(TYPE, EDC_NAMESPACE + "ContractDefinition") + .add(EDC_NAMESPACE + "accessPolicyId", accessPolicyId) + .add(EDC_NAMESPACE + "contractPolicyId", contractPolicyId) + .add(EDC_NAMESPACE + "criteria", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add(TYPE, "CriterionDto") + .add(EDC_NAMESPACE + "operandLeft", EDC_NAMESPACE + "id") + .add(EDC_NAMESPACE + "operator", "=") + .add(EDC_NAMESPACE + "operandRight", assetId) + .build()) + .build()) + .build(); + } +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractNegotiationHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractNegotiationHelperFunctions.java new file mode 100644 index 000000000..d11e176e7 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractNegotiationHelperFunctions.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.spi.monitor.Monitor; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.protocol.dsp.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.mockito.Mockito.mock; + +public class ContractNegotiationHelperFunctions { + + private final static JsonLd jsonLd = new TitaniumJsonLd(mock(Monitor.class)); + + public static JsonObject createNegotiationRequest(String connectorAddress, String providerId, String offerId, String assetId, JsonObject policy) { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "NegotiationInitiateRequestDto") + .add(EDC_NAMESPACE + "connectorId", providerId) + .add(EDC_NAMESPACE + "providerId", providerId) + .add(EDC_NAMESPACE + "connectorAddress", connectorAddress) + .add(EDC_NAMESPACE + "protocol", DATASPACE_PROTOCOL_HTTP) + .add(EDC_NAMESPACE + "offer", Json.createObjectBuilder() + .add(EDC_NAMESPACE + "offerId", offerId) + .add(EDC_NAMESPACE + "assetId", assetId) + .add(EDC_NAMESPACE + "policy", jsonLd.compact(policy).getContent()) + ) + .build(); + } + +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/EdrNegotiationHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/EdrNegotiationHelperFunctions.java new file mode 100644 index 000000000..7b0f85d3a --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/EdrNegotiationHelperFunctions.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.spi.monitor.Monitor; + +import java.util.Set; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.protocol.dsp.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.mockito.Mockito.mock; + +public class EdrNegotiationHelperFunctions { + + private final static JsonLd jsonLd = new TitaniumJsonLd(mock(Monitor.class)); + + public static JsonObject createEdrNegotiationRequest(String connectorAddress, String providerId, String offerId, String assetId, JsonObject policy, JsonArray callbacks) { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "NegotiateEdrRequestDto") + .add(EDC_NAMESPACE + "connectorId", providerId) + .add(EDC_NAMESPACE + "providerId", providerId) + .add(EDC_NAMESPACE + "connectorAddress", connectorAddress) + .add(EDC_NAMESPACE + "protocol", DATASPACE_PROTOCOL_HTTP) + .add(EDC_NAMESPACE + "offer", Json.createObjectBuilder() + .add(EDC_NAMESPACE + "offerId", offerId) + .add(EDC_NAMESPACE + "assetId", assetId) + .add(EDC_NAMESPACE + "policy", jsonLd.compact(policy).getContent()) + ) + .add(EDC_NAMESPACE + "callbackAddresses", callbacks) + .build(); + } + + public static JsonObject createCallback(String url, boolean transactional, Set events) { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "CallbackAddress") + .add(EDC_NAMESPACE + "transactional", transactional) + .add(EDC_NAMESPACE + "uri", url) + .add(EDC_NAMESPACE + "events", events + .stream() + .collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add) + .build()) + .build(); + } + +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java new file mode 100644 index 000000000..7ff41e964 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + + +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import org.eclipse.edc.connector.policy.spi.PolicyDefinition; +import org.eclipse.edc.policy.model.AtomicConstraint; +import org.eclipse.edc.policy.model.Operator; + +import java.util.stream.Stream; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_ACTION_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSTRAINT_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSTRAINT_TYPE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LEFT_OPERAND_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LOGICAL_CONSTRAINT_TYPE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OPERATOR_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OR_CONSTRAINT_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PERMISSION_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_RIGHT_OPERAND_ATTRIBUTE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public class PolicyHelperFunctions { + + private static final String BUSINESS_PARTNER_EVALUATION_KEY = EDC_NAMESPACE + "BusinessPartnerNumber"; + + /** + * Creates a {@link PolicyDefinition} using the given ID, that contains equality constraints for each of the given BusinessPartnerNumbers: + * each BPN is converted into an {@link AtomicConstraint} {@code BusinessPartnerNumber EQ [BPN]}. + */ + public static JsonObject businessPartnerNumberPolicy(String id, String... bpns) { + return policyDefinitionBuilder(bnpPolicy(bpns)) + .add(ID, id) + .build(); + } + + public static JsonObjectBuilder policyDefinitionBuilder() { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "PolicyDefinitionDto"); + } + + public static JsonObjectBuilder policyDefinitionBuilder(JsonObject policy) { + return policyDefinitionBuilder() + .add(EDC_NAMESPACE + "policy", policy); + } + + public static JsonObject noConstraintPolicyDefinition(String id) { + return policyDefinitionBuilder(noConstraintPolicy()) + .add(ID, id) + .build(); + } + + private static JsonObject noConstraintPolicy() { + return Json.createObjectBuilder() + .add(TYPE, "use") + .build(); + } + + private static JsonObject bnpPolicy(String... bnps) { + return Json.createObjectBuilder() + .add(ODRL_PERMISSION_ATTRIBUTE, Json.createArrayBuilder() + .add(permission(bnps))) + .build(); + } + + private static JsonObject permission(String... bpns) { + + var bpnConstraints = Stream.of(bpns) + .map(bpn -> atomicConstraint(BUSINESS_PARTNER_EVALUATION_KEY, Operator.EQ, bpn)) + .collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add); + + return Json.createObjectBuilder() + .add(ODRL_ACTION_ATTRIBUTE, "USE") + .add(ODRL_CONSTRAINT_ATTRIBUTE, Json.createObjectBuilder() + .add(TYPE, ODRL_LOGICAL_CONSTRAINT_TYPE) + .add(ODRL_OR_CONSTRAINT_ATTRIBUTE, bpnConstraints) + .build()) + .build(); + } + + private static JsonObject atomicConstraint(String leftOperand, Operator operator, Object rightOperand) { + return Json.createObjectBuilder() + .add(TYPE, ODRL_CONSTRAINT_TYPE) + .add(ODRL_LEFT_OPERAND_ATTRIBUTE, leftOperand) + .add(ODRL_OPERATOR_ATTRIBUTE, operator.toString()) + .add(ODRL_RIGHT_OPERAND_ATTRIBUTE, rightOperand.toString()) + .build(); + } + + +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/QueryHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/QueryHelperFunctions.java new file mode 100644 index 000000000..df70cbab5 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/QueryHelperFunctions.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonObject; + +import static org.eclipse.edc.api.query.QuerySpecDto.EDC_QUERY_SPEC_LIMIT; +import static org.eclipse.edc.api.query.QuerySpecDto.EDC_QUERY_SPEC_OFFSET; +import static org.eclipse.edc.api.query.QuerySpecDto.EDC_QUERY_SPEC_TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; + +public class QueryHelperFunctions { + + public static JsonObject createQuery(int limit, int offset) { + return Json.createObjectBuilder() + .add(TYPE, EDC_QUERY_SPEC_TYPE) + .add(EDC_QUERY_SPEC_LIMIT, limit) + .add(EDC_QUERY_SPEC_OFFSET, offset) + .build(); + } +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/TransferProcessHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/TransferProcessHelperFunctions.java new file mode 100644 index 000000000..7201c6cb7 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/TransferProcessHelperFunctions.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonObject; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.protocol.dsp.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public class TransferProcessHelperFunctions { + + public static JsonObject createTransferRequest(String dataRequestId, String connectorAddress, String contractId, String assetId, boolean managedResources, JsonObject destination) { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "TransferRequestDto") + .add(ID, dataRequestId) + .add(EDC_NAMESPACE + "dataDestination", destination) + .add(EDC_NAMESPACE + "protocol", DATASPACE_PROTOCOL_HTTP) + .add(EDC_NAMESPACE + "assetId", assetId) + .add(EDC_NAMESPACE + "contractId", contractId) + .add(EDC_NAMESPACE + "connectorAddress", connectorAddress) + .add(EDC_NAMESPACE + "managedResources", managedResources) + + .build(); + + } + + + public static JsonObject createProxyRequest() { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "DataAddress") + .add(EDC_NAMESPACE + "type", "HttpProxy") + .build(); + + } +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/DataWiper.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/DataWiper.java index e99cf0989..45bfba659 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/DataWiper.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/DataWiper.java @@ -47,7 +47,7 @@ public void clearContractDefinitions() { public void clearPolicies() { var ps = context.getService(PolicyDefinitionStore.class); // must .collect() here, otherwise we'll get a ConcurrentModificationException - ps.findAll(QuerySpec.max()).collect(Collectors.toList()).forEach(p -> ps.deleteById(p.getId())); + ps.findAll(QuerySpec.max()).collect(Collectors.toList()).forEach(p -> ps.delete(p.getId())); } public void clearAssetIndex() { diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java index 5bf2a2417..404bfbe1d 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java @@ -19,44 +19,47 @@ import java.util.HashMap; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.IDS_PATH; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.DSP_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_CONNECTOR_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_CONNECTOR_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_DATAPLANE_CONTROL_PORT; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_IDS_API; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_IDS_API_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_DSP_API_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_DSP_CALLBACK; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_MANAGEMENT_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_MANAGEMENT_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_PUBLIC_API_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_CONNECTOR_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_CONNECTOR_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_DATAPLANE_CONTROL_PORT; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_IDS_API; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_IDS_API_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_DSP_API_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_DSP_CALLBACK; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_MANAGEMENT_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_MANAGEMENT_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_PUBLIC_API_PORT; public class MultiRuntimeTest { - + public static final String BPN_SUFFIX = "-BPN"; + public static final String SOKRATES_NAME = "SOKRATES"; + public static final String SOKRATES_BPN = SOKRATES_NAME + BPN_SUFFIX; @RegisterExtension protected static Participant sokrates = new Participant( ":edc-tests:runtime", - "SOKRATES", + SOKRATES_NAME, + SOKRATES_BPN, new HashMap<>() { { put("edc.connector.name", "sokrates"); - put("edc.ids.id", "urn:connector:sokrates"); + put("edc.participant.id", SOKRATES_BPN); put("web.http.port", String.valueOf(SOKRATES_CONNECTOR_PORT)); put("web.http.path", SOKRATES_CONNECTOR_PATH); put("web.http.management.port", String.valueOf(SOKRATES_MANAGEMENT_PORT)); put("web.http.management.path", SOKRATES_MANAGEMENT_PATH); - put("web.http.ids.port", String.valueOf(SOKRATES_IDS_API_PORT)); - put("web.http.ids.path", IDS_PATH); + put("web.http.protocol.port", String.valueOf(SOKRATES_DSP_API_PORT)); + put("web.http.protocol.path", DSP_PATH); + put("edc.dsp.callback.address", SOKRATES_DSP_CALLBACK); put("edc.api.auth.key", "testkey"); - put("ids.webhook.address", SOKRATES_IDS_API); put("web.http.public.path", "/api/public"); put("web.http.public.port", SOKRATES_PUBLIC_API_PORT); @@ -70,25 +73,29 @@ public class MultiRuntimeTest { put("edc.dataplane.selector.httpplane.properties", "{\"publicApiUrl\":\"http://localhost:" + SOKRATES_PUBLIC_API_PORT + "/api/public\"}"); put("edc.receiver.http.dynamic.endpoint", "http://localhost:" + SOKRATES_CONNECTOR_PORT + "/api/consumer/datareference"); put("tractusx.businesspartnervalidation.log.agreement.validation", "true"); + put("edc.agent.identity.key", "BusinessPartnerNumber"); } }); + public static final String PLATO_NAME = "PLATO"; + public static final String PLATO_BPN = PLATO_NAME + BPN_SUFFIX; @RegisterExtension protected static Participant plato = new Participant( ":edc-tests:runtime", - "PLATO", + PLATO_NAME, + PLATO_BPN, new HashMap<>() { { put("edc.connector.name", "plato"); - put("edc.ids.id", "urn:connector:plato"); + put("edc.participant.id", PLATO_BPN); put("web.http.default.port", String.valueOf(PLATO_CONNECTOR_PORT)); put("web.http.default.path", PLATO_CONNECTOR_PATH); put("web.http.management.port", String.valueOf(PLATO_MANAGEMENT_PORT)); put("web.http.management.path", PLATO_MANAGEMENT_PATH); - put("web.http.ids.port", String.valueOf(PLATO_IDS_API_PORT)); - put("web.http.ids.path", IDS_PATH); + put("web.http.protocol.port", String.valueOf(PLATO_DSP_API_PORT)); + put("web.http.protocol.path", DSP_PATH); + put("edc.dsp.callback.address", PLATO_DSP_CALLBACK); put("edc.api.auth.key", "testkey"); - put("ids.webhook.address", PLATO_IDS_API); put("web.http.public.port", PLATO_PUBLIC_API_PORT); put("web.http.public.path", "/api/public"); // embedded dataplane config @@ -100,6 +107,7 @@ public class MultiRuntimeTest { put("edc.dataplane.selector.httpplane.destinationtypes", "HttpProxy"); put("edc.dataplane.selector.httpplane.properties", "{\"publicApiUrl\":\"http://localhost:" + PLATO_PUBLIC_API_PORT + "/api/public\"}"); put("tractusx.businesspartnervalidation.log.agreement.validation", "true"); + put("edc.agent.identity.key", "BusinessPartnerNumber"); } }); } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java index bd1546111..e2546035f 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java @@ -14,28 +14,27 @@ package org.eclipse.tractusx.edc.lifecycle; +import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.specification.RequestSpecification; -import org.eclipse.edc.api.model.IdResponseDto; -import org.eclipse.edc.api.query.QuerySpecDto; -import org.eclipse.edc.catalog.spi.Catalog; -import org.eclipse.edc.connector.api.management.catalog.model.CatalogRequestDto; -import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractNegotiationDto; -import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; -import org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto; -import org.eclipse.edc.connector.api.management.transferprocess.model.TransferProcessDto; -import org.eclipse.edc.connector.api.management.transferprocess.model.TransferRequestDto; -import org.eclipse.edc.connector.policy.spi.PolicyDefinition; +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; import org.eclipse.edc.policy.model.PolicyRegistrationTypes; -import org.eclipse.edc.spi.asset.AssetSelectorExpression; +import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.iam.IdentityService; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.injection.InjectionContainer; import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.edc.spi.types.domain.HttpDataAddress; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.edc.helpers.AssetHelperFunctions; +import org.eclipse.tractusx.edc.helpers.ContractDefinitionHelperFunctions; import org.eclipse.tractusx.edc.token.MockDapsService; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -43,8 +42,6 @@ import java.net.URI; import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -53,31 +50,45 @@ import static io.restassured.http.ContentType.JSON; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.lessThan; +import static org.awaitility.Awaitility.await; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.DCAT_DATASET_ATTRIBUTE; +import static org.eclipse.tractusx.edc.helpers.AssetHelperFunctions.createDataAddressBuilder; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.createCatalogRequest; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.getDatasetAssetId; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.getDatasetContractId; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.getDatasetFirstPolicy; +import static org.eclipse.tractusx.edc.helpers.ContractNegotiationHelperFunctions.createNegotiationRequest; +import static org.eclipse.tractusx.edc.helpers.EdrNegotiationHelperFunctions.createEdrNegotiationRequest; +import static org.eclipse.tractusx.edc.helpers.TransferProcessHelperFunctions.createTransferRequest; +import static org.mockito.Mockito.mock; public class Participant extends EdcRuntimeExtension implements BeforeAllCallback, AfterAllCallback { private final String managementUrl; private final String apiKey; - private final String idsEndpoint; + private final String dspEndpoint; private final TypeManager typeManager = new TypeManager(); - private final String idsId; + private final String runtimeName; private final String bpn; private final String backend; + private final JsonLd jsonLd; + private final Duration timeout = Duration.ofSeconds(30); + + private final ObjectMapper objectMapper = JacksonJsonLd.createObjectMapper(); + private DataWiper wiper; - public Participant(String moduleName, String runtimeName, Map properties) { + public Participant(String moduleName, String runtimeName, String bpn, Map properties) { super(moduleName, runtimeName, properties); this.managementUrl = URI.create(format("http://localhost:%s%s", properties.get("web.http.management.port"), properties.get("web.http.management.path"))).toString(); - this.idsEndpoint = URI.create(format("http://localhost:%s%s", properties.get("web.http.ids.port"), properties.get("web.http.ids.path"))).toString(); + this.dspEndpoint = URI.create(format("http://localhost:%s%s", properties.get("web.http.protocol.port"), properties.get("web.http.protocol.path"))).toString(); this.apiKey = properties.get("edc.api.auth.key"); - this.idsId = properties.get("edc.ids.id"); - this.bpn = runtimeName + "-BPN"; + this.bpn = bpn; + this.runtimeName = runtimeName; this.backend = properties.get("edc.receiver.http.dynamic.endpoint"); this.registerServiceMock(IdentityService.class, new MockDapsService(getBpn())); - + jsonLd = new TitaniumJsonLd(mock(Monitor.class)); typeManager.registerTypes(PolicyRegistrationTypes.TYPES.toArray(Class[]::new)); } @@ -106,52 +117,27 @@ public void afterAll(ExtensionContext context) throws Exception { /** * Creates an asset with the given ID and props using the participant's Data Management API */ - public void createAsset(String id, Map properties) { - properties = new HashMap<>(properties); - properties.put("asset:prop:id", id); - properties.put("asset:prop:description", "test description"); - - var asset = Map.of( - "asset", Map.of( - "id", id, - "properties", properties - ), - "dataAddress", Map.of( - "properties", Map.of("type", "test-type") - - ) - ); - - baseRequest() - .body(asset) - .when() - .post("/assets") - .then() - .statusCode(200) - .contentType(JSON); + public void createAsset(String id, JsonObject properties) { + createAsset(id, properties, createDataAddressBuilder("test-type").build()); + } + /** + * Creates an asset with the given ID and props using the participant's Data Management API + */ + public void createAsset(String id) { + createAsset(id, Json.createObjectBuilder().build(), createDataAddressBuilder("test-type").build()); } /** * Creates an asset with the given ID and props using the participant's Data Management API */ - public void createAsset(String id, Map asserProperties, HttpDataAddress address) { - asserProperties = new HashMap<>(asserProperties); - asserProperties.put("asset:prop:id", id); - asserProperties.put("asset:prop:description", "test description"); - - var asset = Map.of( - "asset", Map.of( - "id", id, - "properties", asserProperties - ), - "dataAddress", address - ); + public void createAsset(String id, JsonObject assetProperties, JsonObject dataAddress) { + var asset = AssetHelperFunctions.createAsset(id, assetProperties, dataAddress); baseRequest() .body(asset) .when() - .post("/assets") + .post("/v2/assets") .then() .statusCode(200) .contentType(JSON); @@ -160,110 +146,94 @@ public void createAsset(String id, Map asserProperties, HttpData /** * Creates a {@link org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition} using the participant's Data Management API */ - public void createContractDefinition(String assetId, String definitionId, String accessPolicyId, String contractPolicyId, long contractValidityDurationSeconds) { - var contractDefinition = Map.of( - "id", definitionId, - "accessPolicyId", accessPolicyId, - "validity", String.valueOf(contractValidityDurationSeconds), - "contractPolicyId", contractPolicyId, - "criteria", AssetSelectorExpression.Builder.newInstance().constraint("asset:prop:id", "=", assetId).build().getCriteria() - ); + public void createContractDefinition(String assetId, String definitionId, String accessPolicyId, String contractPolicyId) { + var requestBody = ContractDefinitionHelperFunctions.createContractDefinition(assetId, definitionId, accessPolicyId, contractPolicyId); baseRequest() - .body(contractDefinition) + .contentType(JSON) + .body(requestBody) .when() - .post("/contractdefinitions") + .post("/v2/contractdefinitions") .then() .statusCode(200) - .contentType(JSON).contentType(JSON); + .contentType(JSON); } - /** - * Creates a {@link PolicyDefinition} using the participant's Data Management API - */ - public void createPolicy(PolicyDefinition policyDefinition) { + public void createPolicy(JsonObject policyDefinition) { baseRequest() + .contentType(JSON) .body(policyDefinition) .when() - .post("/policydefinitions") + .post("/v2/policydefinitions") .then() .statusCode(200) - .contentType(JSON).contentType(JSON); + .contentType(JSON); } - /** - * Requests the {@link Catalog} from another participant using this participant's Data Management API - */ - public Catalog requestCatalog(Participant other) { - return requestCatalog(other, QuerySpecDto.Builder.newInstance().build()); - } + public String negotiateContract(Participant other, String assetId) { + var dataset = getDatasetForAsset(other, assetId); + assertThat(dataset).withFailMessage("Catalog received from " + other.runtimeName + " was empty!").isNotEmpty(); - /** - * Requests the {@link Catalog} from another participant using this participant's Data Management API - */ - public Catalog requestCatalog(Participant other, QuerySpecDto query) { + var policy = getDatasetFirstPolicy(dataset); + var contractId = getDatasetContractId(dataset); + var requestBody = createNegotiationRequest(other.dspEndpoint, other.getBpn(), contractId.toString(), contractId.assetIdPart(), policy); var response = baseRequest() .when() - .body(CatalogRequestDto.Builder.newInstance() - .providerUrl(other.idsEndpoint + "/data") - .querySpec(query) - .build()) - .post("/catalog/request") + .body(requestBody) + .post("/v2/contractnegotiations") .then(); - var code = response.extract().statusCode(); var body = response.extract().body().asString(); + assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); - // doing an assertJ style assertion will allow us to use the body as fail message if the return code != 200 - assertThat(code).withFailMessage(body).isEqualTo(200); - return typeManager.readValue(body, Catalog.class); + return response.extract().jsonPath().getString(ID); } - public String negotiateContract(Participant other, String assetId) { - var catalog = requestCatalog(other); - assertThat(catalog.getContractOffers()).withFailMessage("Catalog received from " + other.idsId + " was empty!").isNotEmpty(); + public void negotiateEdr(Participant other, String assetId, JsonArray callbacks) { + var dataset = getDatasetForAsset(other, assetId); + assertThat(dataset).withFailMessage("Catalog received from " + other.runtimeName + " was empty!").isNotEmpty(); + + var policy = getDatasetFirstPolicy(dataset); + var contractId = getDatasetContractId(dataset); + + var requestBody = createEdrNegotiationRequest(other.dspEndpoint, other.getBpn(), contractId.toString(), contractId.assetIdPart(), policy, callbacks); + + var response = baseRequest() .when() - .body(NegotiationInitiateRequestDto.Builder.newInstance() - .connectorAddress(other.idsEndpoint + "/data") - .connectorId(getBpn()) - .offer(catalog.getContractOffers().stream().filter(o -> o.getAsset().getId().equals(assetId)) - .findFirst().map(co -> ContractOfferDescription.Builder.newInstance() - .assetId(assetId) - .offerId(co.getId()) - .policy(co.getPolicy()) - .validity(ChronoUnit.SECONDS.between(co.getContractStart(), co.getContractEnd().plus(Duration.ofMillis(500)))) // the plus 1 is required due to https://github.com/eclipse-edc/Connector/issues/2650 - .build()) - .orElseThrow((() -> new RuntimeException("A contract for assetId " + assetId + " could not be negotiated")))) - .build() - ) - .post("/contractnegotiations") + .body(requestBody) + .post("/adapter/edrs") .then(); var body = response.extract().body().asString(); assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); - return typeManager.readValue(body, IdResponseDto.class).getId(); } - public ContractNegotiationDto getNegotiation(String negotiationId) { - var response = baseRequest() + public String getNegotiationState(String negotiationId) { + return baseRequest() .when() - .get("/contractnegotiations/" + negotiationId) - .then(); + .get("/v2/contractnegotiations/{id}/state", negotiationId) + .then() + .statusCode(200) + .extract().body().jsonPath().getString("'edc:state'"); + } - var body = response.extract().body().asString(); - assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); - return typeManager.readValue(body, ContractNegotiationDto.class); + public String getContractAgreementId(String negotiationId) { + return getContractNegotiationField(negotiationId, "contractAgreementId"); } - /** - * Returns this participant's IDS ID - */ - public String idsId() { - return idsId; + private String getContractNegotiationField(String negotiationId, String fieldName) { + return baseRequest() + .when() + .get("/v2/contractnegotiations/{id}", negotiationId) + .then() + .statusCode(200) + .extract().body().jsonPath() + .getString(format("'edc:%s'", fieldName)); } + /** * Returns this participant's BusinessPartnerNumber (=BPN). This is constructed of the runtime name plus "-BPN" */ @@ -271,38 +241,29 @@ public String getBpn() { return bpn; } - public String requestTransfer(String contractId, String assetId, Participant other, DataAddress destination, String dataRequestId) { + public String requestTransfer(String dataRequestId, String contractId, String assetId, Participant other, JsonObject destination) { + + var request = createTransferRequest(dataRequestId, other.dspEndpoint, contractId, assetId, false, destination); var response = baseRequest() .when() - .body(TransferRequestDto.Builder.newInstance() - .assetId(assetId) - .id(dataRequestId) - .connectorAddress(other.idsEndpoint + "/data") - .managedResources(false) - .contractId(contractId) - .connectorId(bpn) - .protocol("ids-multipart") - .dataDestination(destination) - .build()) - .post("/transferprocess") + .body(request) + .post("/v2/transferprocesses") .then(); var body = response.extract().body().asString(); assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); - return typeManager.readValue(body, IdResponseDto.class).getId(); + return response.extract().jsonPath().getString(ID); + } - public TransferProcessDto getTransferProcess(String transferProcessId) { - var json = baseRequest() + public String getTransferProcessState(String id) { + return baseRequest() .when() - .get("/transferprocess/" + transferProcessId) + .get("/v2/transferprocesses/{id}/state", id) .then() - .statusCode(allOf(greaterThanOrEqualTo(200), lessThan(300))) - .extract().body().asString(); - - return typeManager.readValue(json, TransferProcessDto.class); - + .statusCode(200) + .extract().body().jsonPath().getString("'edc:state'"); } public EndpointDataReference getDataReference(String dataRequestId) { @@ -332,6 +293,48 @@ public String pullData(EndpointDataReference edr, Map queryParam return response.body().asString(); } + public JsonArray getCatalogDatasets(Participant provider) { + return getCatalogDatasets(provider, null); + } + + public JsonArray getCatalogDatasets(Participant provider, JsonObject querySpec) { + var datasetReference = new AtomicReference(); + + var requestBody = createCatalogRequest(querySpec, provider.dspEndpoint); + + await().atMost(timeout).untilAsserted(() -> { + var response = baseRequest() + .contentType(JSON) + .when() + .body(requestBody) + .post("/v2/catalog/request") + .then() + .statusCode(200) + .extract().body().asString(); + + var responseBody = objectMapper.readValue(response, JsonObject.class); + + var catalog = jsonLd.expand(responseBody).orElseThrow(f -> new EdcException(f.getFailureDetail())); + + var datasets = catalog.getJsonArray(DCAT_DATASET_ATTRIBUTE); + assertThat(datasets).hasSizeGreaterThan(0); + + datasetReference.set(datasets); + }); + + return datasetReference.get(); + } + + public JsonObject getDatasetForAsset(Participant provider, String assetId) { + var datasets = getCatalogDatasets(provider); + return datasets.stream() + .map(JsonValue::asJsonObject) + .filter(it -> assetId.equals(getDatasetAssetId(it))) + .findFirst() + .orElseThrow(() -> new EdcException(format("No dataset for asset %s in the catalog", assetId))); + } + + @Override protected void bootExtensions(ServiceExtensionContext context, List> serviceExtensions) { super.bootExtensions(context, serviceExtensions); diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java index f865d39d9..234de574e 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java @@ -19,25 +19,27 @@ class TestRuntimeConfiguration { - static final String IDS_PATH = "/api/v1/ids"; + static final String DSP_PATH = "/api/v1/dsp"; static final int PLATO_CONNECTOR_PORT = getFreePort(); static final int PLATO_MANAGEMENT_PORT = getFreePort(); static final String PLATO_CONNECTOR_PATH = "/api"; static final String PLATO_MANAGEMENT_PATH = "/api/v1/management"; - static final int PLATO_IDS_API_PORT = getFreePort(); - static final String PLATO_IDS_API = "http://localhost:" + PLATO_IDS_API_PORT; + + static final int PLATO_DSP_API_PORT = getFreePort(); + + static final String PLATO_DSP_CALLBACK = "http://localhost:" + PLATO_DSP_API_PORT + DSP_PATH; static final int SOKRATES_CONNECTOR_PORT = getFreePort(); static final int SOKRATES_MANAGEMENT_PORT = getFreePort(); static final String SOKRATES_CONNECTOR_PATH = "/api"; static final String SOKRATES_MANAGEMENT_PATH = "/api/v1/management"; - static final int SOKRATES_IDS_API_PORT = getFreePort(); - static final String SOKRATES_IDS_API = "http://localhost:" + SOKRATES_IDS_API_PORT; - + static final int SOKRATES_DSP_API_PORT = getFreePort(); + static final String SOKRATES_DSP_CALLBACK = "http://localhost:" + SOKRATES_DSP_API_PORT + DSP_PATH; static final String SOKRATES_PUBLIC_API_PORT = String.valueOf(getFreePort()); static final String PLATO_PUBLIC_API_PORT = String.valueOf(getFreePort()); static final String PLATO_DATAPLANE_CONTROL_PORT = String.valueOf(getFreePort()); static final String SOKRATES_DATAPLANE_CONTROL_PORT = String.valueOf(getFreePort()); + } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java deleted file mode 100644 index 56d92afbe..000000000 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.tractusx.edc.policy; - - -import org.eclipse.edc.connector.policy.spi.PolicyDefinition; -import org.eclipse.edc.policy.model.Action; -import org.eclipse.edc.policy.model.AtomicConstraint; -import org.eclipse.edc.policy.model.Constraint; -import org.eclipse.edc.policy.model.LiteralExpression; -import org.eclipse.edc.policy.model.Operator; -import org.eclipse.edc.policy.model.OrConstraint; -import org.eclipse.edc.policy.model.Permission; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.policy.model.PolicyType; - -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class PolicyHelperFunctions { - /** - * Creates a {@link PolicyDefinition} using the given ID, that contains equality constraints for each of the given BusinessPartnerNumbers: - * each BPN is converted into an {@link AtomicConstraint} {@code BusinessPartnerNumber EQ [BPN]}. - */ - public static PolicyDefinition businessPartnerNumberPolicy(String id, String... bpns) { - - var bpnConstraints = Stream.of(bpns) - .map(bpn -> (Constraint) AtomicConstraint.Builder.newInstance() - .leftExpression(new LiteralExpression("BusinessPartnerNumber")) - .operator(Operator.EQ) - .rightExpression(new LiteralExpression(bpn)) - .build()) - .collect(Collectors.toList()); - - return PolicyDefinition.Builder.newInstance() - .id(id) - .policy(Policy.Builder.newInstance() - .permission(Permission.Builder.newInstance() - .action(Action.Builder.newInstance().type("USE").build()) - .constraint(OrConstraint.Builder.newInstance() - .constraints(bpnConstraints) - .build()) - .build()).build()).build(); - } - - public static PolicyDefinition noConstraintPolicy(String id) { - return PolicyDefinition.Builder.newInstance() - .id(id) - .policy(Policy.Builder.newInstance() - .permission(Permission.Builder.newInstance() - .action(Action.Builder.newInstance().type("USE").build()) - .build()) - .type(PolicyType.SET) - .build()) - .build(); - } -} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java index ec8045f5b..1a9149099 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java @@ -15,18 +15,18 @@ package org.eclipse.tractusx.edc.tests; -import org.eclipse.edc.api.query.QuerySpecDto; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.tractusx.edc.lifecycle.MultiRuntimeTest; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.Map; - import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.tractusx.edc.policy.PolicyHelperFunctions.businessPartnerNumberPolicy; -import static org.eclipse.tractusx.edc.policy.PolicyHelperFunctions.noConstraintPolicy; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.getDatasetAssetId; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.getDatasetPolicies; +import static org.eclipse.tractusx.edc.helpers.PolicyHelperFunctions.businessPartnerNumberPolicy; +import static org.eclipse.tractusx.edc.helpers.PolicyHelperFunctions.noConstraintPolicyDefinition; +import static org.eclipse.tractusx.edc.helpers.QueryHelperFunctions.createQuery; @EndToEndTest public class CatalogTest extends MultiRuntimeTest { @@ -34,23 +34,21 @@ public class CatalogTest extends MultiRuntimeTest { @Test void requestCatalog_fulfillsPolicy_shouldReturnOffer() { // arrange - sokrates.createAsset("test-asset", Map.of("fooprop", "fooval")); - var accessPolicy = noConstraintPolicy("test-ap1"); - var contractPolicy = noConstraintPolicy("test-cp1"); + sokrates.createAsset("test-asset"); + var accessPolicy = noConstraintPolicyDefinition("test-ap1"); + var contractPolicy = noConstraintPolicyDefinition("test-cp1"); sokrates.createPolicy(accessPolicy); sokrates.createPolicy(contractPolicy); - sokrates.createContractDefinition("test-asset", "test-def", "test-ap1", "test-cp1", 60); + sokrates.createContractDefinition("test-asset", "test-def", "test-ap1", "test-cp1"); // act - var catalog = plato.requestCatalog(sokrates); + var catalog = plato.getCatalogDatasets(sokrates); // assert - assertThat(catalog.getContractOffers()).isNotEmpty() + assertThat(catalog).isNotEmpty() .hasSize(1) .allSatisfy(co -> { - assertThat(co.getAsset().getId()).isEqualTo("test-asset"); - assertThat(co.getProvider().toString()).isEqualTo(sokrates.idsId()); - assertThat(co.getConsumer().toString()).isEqualTo(plato.idsId()); + assertThat(getDatasetAssetId(co)).isEqualTo("test-asset"); }); } @@ -58,66 +56,71 @@ void requestCatalog_fulfillsPolicy_shouldReturnOffer() { @Test @DisplayName("Verify that Plato receives only the offers he is permitted to") void requestCatalog_filteredByBpn_shouldReject() { - var onlyPlatoPolicy = businessPartnerNumberPolicy("ap", "BPN1", "BPN2", plato.getBpn()); - var onlyDiogenesPolicy = businessPartnerNumberPolicy("dp", "ARISTOTELES-BPN"); + var onlyPlatoId = "ap"; + var onlyDiogenesId = "db"; + + var onlyPlatoPolicy = businessPartnerNumberPolicy(onlyPlatoId, "BPN1", "BPN2", plato.getBpn()); + var onlyDiogenesPolicy = businessPartnerNumberPolicy(onlyDiogenesId, "ARISTOTELES-BPN"); var noConstraintPolicyId = "no-constraint"; sokrates.createPolicy(onlyPlatoPolicy); - sokrates.createPolicy(noConstraintPolicy(noConstraintPolicyId)); + sokrates.createPolicy(onlyDiogenesPolicy); + sokrates.createPolicy(noConstraintPolicyDefinition(noConstraintPolicyId)); - sokrates.createAsset("test-asset1", Map.of("canSee", "true")); - sokrates.createAsset("test-asset2", Map.of("canSee", "true")); - sokrates.createAsset("test-asset3", Map.of("canSee", "false")); + sokrates.createAsset("test-asset1"); + sokrates.createAsset("test-asset2"); + sokrates.createAsset("test-asset3"); - sokrates.createContractDefinition("test-asset1", "def1", noConstraintPolicyId, noConstraintPolicyId, 60); - sokrates.createContractDefinition("test-asset2", "def2", onlyPlatoPolicy.getId(), noConstraintPolicyId, 60); - sokrates.createContractDefinition("test-asset3", "def3", onlyDiogenesPolicy.getId(), noConstraintPolicyId, 60); + sokrates.createContractDefinition("test-asset1", "def1", noConstraintPolicyId, noConstraintPolicyId); + sokrates.createContractDefinition("test-asset2", "def2", onlyPlatoId, noConstraintPolicyId); + sokrates.createContractDefinition("test-asset3", "def3", onlyDiogenesId, noConstraintPolicyId); // act - var catalog = plato.requestCatalog(sokrates); - assertThat(catalog.getContractOffers()).hasSize(2); + var catalog = plato.getCatalogDatasets(sokrates); + assertThat(catalog).hasSize(2); } @Test @DisplayName("Multiple ContractDefinitions exist for one Asset") void requestCatalog_multipleOffersForAsset() { - sokrates.createAsset("asset-1", Map.of("test-key", "test-val")); - sokrates.createPolicy(noConstraintPolicy("policy-1")); + sokrates.createAsset("asset-1"); + sokrates.createPolicy(noConstraintPolicyDefinition("policy-1")); sokrates.createPolicy(businessPartnerNumberPolicy("policy-2", plato.getBpn())); - sokrates.createContractDefinition("asset-1", "def1", "policy-1", "policy-1", 60); - sokrates.createContractDefinition("asset-1", "def2", "policy-2", "policy-1", 60); + sokrates.createContractDefinition("asset-1", "def1", "policy-1", "policy-1"); + sokrates.createContractDefinition("asset-1", "def2", "policy-2", "policy-1"); - var catalog = plato.requestCatalog(sokrates); - assertThat(catalog.getContractOffers()).hasSize(2) - .allSatisfy(cd -> assertThat(cd.getAsset().getId()).isEqualTo("asset-1")) - // .hasToString is advisable as it handles NPEs better: - .allSatisfy(cd -> assertThat(cd.getConsumer()).hasToString(plato.idsId())) - .allSatisfy(cd -> assertThat(cd.getProvider()).hasToString(sokrates.idsId())); + var catalog = plato.getCatalogDatasets(sokrates); + assertThat(catalog).hasSize(1) + .allSatisfy(cd -> { + assertThat(getDatasetAssetId(cd)).isEqualTo("asset-1"); + assertThat(getDatasetPolicies(cd)).hasSize(2); + }); } @Test @DisplayName("Catalog with 1000 offers") void requestCatalog_of1000Assets_shouldContainAll() { - var policy = businessPartnerNumberPolicy("policy-1", plato.getBpn()); + var policyId = "policy-1"; + var policy = businessPartnerNumberPolicy(policyId, plato.getBpn()); sokrates.createPolicy(policy); - sokrates.createPolicy(noConstraintPolicy("noconstraint")); + sokrates.createPolicy(noConstraintPolicyDefinition("noconstraint")); range(0, 1000) .forEach(i -> { var assetId = "asset-" + i; - sokrates.createAsset(assetId, Map.of()); - sokrates.createContractDefinition(assetId, "def-" + i, policy.getId(), "noconstraint", 60); + sokrates.createAsset(assetId); + sokrates.createContractDefinition(assetId, "def-" + i, policyId, "noconstraint"); }); // request all at once - var o = plato.requestCatalog(sokrates, QuerySpecDto.Builder.newInstance().limit(1000).offset(0).build()).getContractOffers(); - assertThat(o).hasSize(1000); + var dataset = plato.getCatalogDatasets(sokrates, createQuery(1000, 0)); + assertThat(dataset).hasSize(1000); // request in chunks - var o2 = plato.requestCatalog(sokrates, QuerySpecDto.Builder.newInstance().limit(500).offset(0).build()).getContractOffers(); - var o3 = plato.requestCatalog(sokrates, QuerySpecDto.Builder.newInstance().limit(500).offset(500).build()).getContractOffers(); + var o2 = plato.getCatalogDatasets(sokrates, createQuery(500, 0)); + var o3 = plato.getCatalogDatasets(sokrates, createQuery(500, 500)); assertThat(o2).doesNotContainAnyElementsOf(o3); } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java index 1f93ae5e7..7ab0419e9 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java @@ -14,14 +14,12 @@ package org.eclipse.tractusx.edc.tests; +import jakarta.json.Json; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; -import org.eclipse.edc.connector.api.management.transferprocess.model.TransferProcessDto; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates; import org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates; import org.eclipse.edc.junit.annotations.EndToEndTest; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.edc.spi.types.domain.HttpDataAddress; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.edc.lifecycle.MultiRuntimeTest; import org.junit.jupiter.api.AfterEach; @@ -37,14 +35,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.awaitility.pollinterval.FibonacciPollInterval.fibonacci; -import static org.eclipse.edc.connector.transfer.dataplane.spi.TransferDataPlaneConstants.HTTP_PROXY; -import static org.eclipse.tractusx.edc.policy.PolicyHelperFunctions.businessPartnerNumberPolicy; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.tractusx.edc.helpers.PolicyHelperFunctions.businessPartnerNumberPolicy; +import static org.eclipse.tractusx.edc.helpers.TransferProcessHelperFunctions.createProxyRequest; @EndToEndTest public class HttpConsumerPullWithProxyTest extends MultiRuntimeTest { private static final Duration ASYNC_TIMEOUT = ofSeconds(45); private static final Duration ASYNC_POLL_INTERVAL = ofSeconds(1); - private final long ONE_WEEK = 60 * 60 * 24 * 7; MockWebServer server = new MockWebServer(); @Test @@ -55,15 +53,19 @@ void transferData_privateBackend() throws IOException, InterruptedException { var authCodeHeaderName = "test-authkey"; var authCode = "test-authcode"; - plato.createAsset(assetId, Map.of(), HttpDataAddress.Builder.newInstance() - .contentType("application/json") - .baseUrl(url.toString()) - .authKey(authCodeHeaderName) - .authCode(authCode) - .build()); + var dataAddress = Json.createObjectBuilder() + .add(EDC_NAMESPACE + "type", "HttpData") + .add(EDC_NAMESPACE + "contentType", "application/json") + .add(EDC_NAMESPACE + "baseUrl", url.toString()) + .add(EDC_NAMESPACE + "authKey", authCodeHeaderName) + .add(EDC_NAMESPACE + "authCode", authCode) + .build(); + + plato.createAsset(assetId, Json.createObjectBuilder().build(), dataAddress); + plato.createPolicy(businessPartnerNumberPolicy("policy-1", sokrates.getBpn())); plato.createPolicy(businessPartnerNumberPolicy("policy-2", sokrates.getBpn())); - plato.createContractDefinition(assetId, "def-1", "policy-1", "policy-2", ONE_WEEK); + plato.createContractDefinition(assetId, "def-1", "policy-1", "policy-2"); var negotiationId = sokrates.negotiateContract(plato, assetId); // forward declarations of our actual values @@ -72,18 +74,19 @@ void transferData_privateBackend() throws IOException, InterruptedException { var contractAgreementId = new AtomicReference(); var edr = new AtomicReference(); - // wait for the successful contract negotiation await().pollInterval(ASYNC_POLL_INTERVAL) .atMost(ASYNC_TIMEOUT) .untilAsserted(() -> { - var negotiation = sokrates.getNegotiation(negotiationId); - assertThat(negotiation.getState()).isEqualTo(ContractNegotiationStates.CONFIRMED.toString()); - contractAgreementId.set(negotiation.getContractAgreementId()); - assertThat(contractAgreementId).isNotNull(); - transferProcessId.set(sokrates.requestTransfer(contractAgreementId.get(), assetId, plato, DataAddress.Builder.newInstance() - .type(HTTP_PROXY) - .build(), dataRequestId)); + var negotiationState = sokrates.getNegotiationState(negotiationId); + assertThat(negotiationState).isEqualTo(ContractNegotiationStates.FINALIZED.toString()); + + var agreementId = sokrates.getContractAgreementId(negotiationId); + assertThat(agreementId).isNotNull(); + contractAgreementId.set(agreementId); + + var tpId = sokrates.requestTransfer(dataRequestId, contractAgreementId.get(), assetId, plato, createProxyRequest()); + transferProcessId.set(tpId); assertThat(transferProcessId).isNotNull(); }); @@ -91,9 +94,8 @@ void transferData_privateBackend() throws IOException, InterruptedException { await().pollInterval(fibonacci()) .atMost(ASYNC_TIMEOUT) .untilAsserted(() -> { - var tp = sokrates.getTransferProcess(transferProcessId.get()); - assertThat(tp).isNotNull() - .extracting(TransferProcessDto::getState).isEqualTo(TransferProcessStates.COMPLETED.toString()); + var tpState = sokrates.getTransferProcessState(transferProcessId.get()); + assertThat(tpState).isNotNull().isEqualTo(TransferProcessStates.COMPLETED.toString()); }); // wait until EDC is available on the consumer side diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/NegotiateEdrTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/NegotiateEdrTest.java new file mode 100644 index 000000000..b10b4fa8a --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/NegotiateEdrTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.tests; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationAgreed; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFinalized; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationInitiated; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationRequested; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationVerified; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessCompleted; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessInitiated; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessProvisioned; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessRequested; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessStarted; +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.tractusx.edc.lifecycle.MultiRuntimeTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.tractusx.edc.helpers.EdrNegotiationHelperFunctions.createCallback; +import static org.eclipse.tractusx.edc.helpers.PolicyHelperFunctions.businessPartnerNumberPolicy; + +@EndToEndTest +public class NegotiateEdrTest extends MultiRuntimeTest { + + MockWebServer server = new MockWebServer(); + + ObjectMapper mapper = new ObjectMapper(); + + + @Test + @DisplayName("Verify that the callbacks are invoked when negotiating an EDR") + void negotiateEdr_shouldInvokeCallbacks() throws IOException { + + var expectedEvents = List.of( + createEvent(ContractNegotiationInitiated.class), + createEvent(ContractNegotiationRequested.class), + createEvent(ContractNegotiationAgreed.class), + createEvent(ContractNegotiationFinalized.class), + createEvent(ContractNegotiationVerified.class), + createEvent(TransferProcessInitiated.class), + createEvent(TransferProcessProvisioned.class), + createEvent(TransferProcessRequested.class), + createEvent(TransferProcessStarted.class), + createEvent(TransferProcessCompleted.class)); + + var assetId = "api-asset-1"; + var url = server.url("/mock/api"); + server.start(); + + var authCodeHeaderName = "test-authkey"; + var authCode = "test-authcode"; + plato.createAsset(assetId, Json.createObjectBuilder().build(), Json.createObjectBuilder() + .add(EDC_NAMESPACE + "type", "HttpData") + .add(EDC_NAMESPACE + "contentType", "application/json") + .add(EDC_NAMESPACE + "baseUrl", url.toString()) + .add(EDC_NAMESPACE + "authKey", authCodeHeaderName) + .add(EDC_NAMESPACE + "authCode", authCode) + .build()); + + plato.createPolicy(businessPartnerNumberPolicy("policy-1", sokrates.getBpn())); + plato.createPolicy(businessPartnerNumberPolicy("policy-2", sokrates.getBpn())); + plato.createContractDefinition(assetId, "def-1", "policy-1", "policy-2"); + + + expectedEvents.forEach(_event -> { + server.enqueue(new MockResponse()); + }); + + var callbacks = Json.createArrayBuilder() + .add(createCallback(url.toString(), true, Set.of("contract.negotiation", "transfer.process"))) + .build(); + + sokrates.negotiateEdr(plato, assetId, callbacks); + + var events = expectedEvents.stream() + .map(this::waitForEvent) + .collect(Collectors.toList()); + + assertThat(expectedEvents).usingRecursiveFieldByFieldElementComparator().containsAll(events); + } + + ReceivedEvent createEvent(Class klass) { + return ReceivedEvent.Builder.newInstance().type(klass.getSimpleName()).build(); + } + + ReceivedEvent waitForEvent(ReceivedEvent event) { + try { + var request = server.takeRequest(20, TimeUnit.SECONDS); + if (request != null) { + return mapper.readValue(request.getBody().inputStream(), ReceivedEvent.class); + } else { + throw new RuntimeException("Timeout exceeded waiting for events"); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private static class ReceivedEvent { + private String type; + + public String getType() { + return type; + } + + @Override + public String toString() { + return "ReceivedEvent{" + + "type='" + type + '\'' + + '}'; + } + + public static class Builder { + private final ReceivedEvent event; + + private Builder(ReceivedEvent event) { + this.event = event; + } + + public static Builder newInstance() { + return new Builder(new ReceivedEvent()); + } + + public Builder type(String type) { + this.event.type = type; + return this; + } + + public ReceivedEvent build() { + return event; + } + } + + + } +} diff --git a/edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts b/edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts new file mode 100644 index 000000000..50bd6598d --- /dev/null +++ b/edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + testImplementation(libs.edc.junit) + testImplementation(libs.restAssured) + testImplementation(libs.okhttp.mockwebserver) + + // test runtime config + testImplementation(libs.edc.config.filesystem) + testImplementation(libs.edc.vault.filesystem) + testImplementation(libs.edc.dpf.http) + testImplementation(project(":spi:edr-cache-spi")) + testImplementation(project(":core:edr-cache-core")) + testImplementation(project(":edc-dataplane:edc-dataplane-proxy-consumer-api")) + testImplementation(project(":edc-dataplane:edc-dataplane-proxy-provider-api")) + testImplementation(project(":edc-dataplane:edc-dataplane-proxy-provider-core")) + +} + + + diff --git a/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java new file mode 100644 index 000000000..c7649ea67 --- /dev/null +++ b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.e2e; + +import io.restassured.specification.RequestSpecification; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static io.restassured.RestAssured.given; +import static java.lang.String.format; +import static java.lang.String.valueOf; +import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; +import static org.eclipse.tractusx.edc.dataplane.proxy.e2e.EdrCacheSetup.createEntries; +import static org.eclipse.tractusx.edc.dataplane.proxy.e2e.KeyStoreSetup.createKeyStore; +import static org.eclipse.tractusx.edc.dataplane.proxy.e2e.VaultSetup.createVaultStore; +import static org.hamcrest.Matchers.is; + + +/** + * Performs end-to-end testing using a consumer data plane, a producer data plane, and a proxied HTTP endpoint. + *

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

    + *
  • One EDR is for the {@link #SINGLE_TRANSFER_ID} transfer process that is associated with a single contract agreement for the {@link #SINGLE_ASSET_ID} + * asset
  • + *
  • Two EDRs for transfer processes that are associated with contract agreements for the same asset, {@link #MULTI_ASSET_ID}
  • + *
+ *

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

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